简体   繁体   中英

C++ - return different variable types

I don't know if this is possible but maybe there are other solutions to what I want. I am trying to get settings from a settings file. They can be strings (like names), integers or booleans. Of course, they are stored as text inside the file but I will create a class for opening and returning settings, yet not as string but as what each one of them are in fact.

class Settings {
    public:
        Settings(string FileName);
        template <class T> T Setting(string SettingName);
}

The constructor would load the file, will parse the settings and store them as a map, for example. Now, when I call the Setting member function I want it to identify what type is the value of the requested setting (if it is numeric, a integer, if is "true" or "false" a boolean, if is alphanumeric a string) and return a value of that type. An example

Settings UserPreferences("Preferences.cfg");
bool AutoLogin = UserPreferences.Setting("autologin");  // return bool
string UserName = UserPreferences.Setting("username"); // return string or char*

I had a look over templates but it looks like I have to specify what variable I expect when creating the Settings object but that's not the point. I am happy with declaring the type of the variable to return like this:

bool AutoLogin = UserPreferences.Setting<bool>("autologin");
string UserName = UserPreferences.Setting<string>("username");

but I don't know if that is possible. What do you think?

This is definitely possible, although you have to have some guarantee that it can cast to the given type. This is seen a lot in XNA's ContentLoader (albeit a much different system). You can use this approach to simplify and abstract how things are stored an retrieved. Consider:

class Loader
{
private:
    vector<void*> _items;
public:
    template <typename Type>
    Type GetItem( int index ) { return (Type)(_items[ index ]); }
};

The idea is that as long as you can cast the internal data to the requested type reliably (more reliably than the example) than it is a perfectly legal operation. How to make that a guaranteed success is another question entirely, but you can definitely have methods whose return type is that of the their template types. Consider the following example (I used this is a college project for a resource loader):

Header.h

class BasicResource
{
public:
    static const int ResourceID;
    const int ID;
    BasicResource( )
        : ID( ResourceID )
    {
    }
};

class Loader
{
private:
    vector<BasicResource*> _items;
public:
    template <typename Type>
    Type GetItem( int index );
};

#include "inline.inl"

Inline.inl

template <typename Type>
Type Loader::GetItem( int index )
{
    auto item = _items[ index ];
    if( item != nullptr && item->ID == Type::ResourceID )
    {
        return (Type)_item;
    }
    else
    {
        // Handle the fail case somehow
    }
}

Inline files allow you to seperate your logic as you normally would, but include it in the header which allows for export of template methods.

Yes, it is certainly possible. I've written the following bit of complete code to prove the point:

#include <iostream>
#include <map>
#include <string>
#include <sstream>
#include <stdexcept>


struct Settings
{
  typedef std::map<std::string, std::string> SettingsMap;
  template <class T> T as( const std::string& name ) const
  {
    std::istringstream is( getEntry( name ) );
    T value;
    if( is )
    {
      if( (is >> value) || (is.eof() && !is.fail()) )
      {
        return value;
      }
    }
   //Exception handling not in scope of question
   throw std::runtime_error( "..." );
};

const std::string& getEntry( const std::string& name ) const
{
  SettingsMap::const_iterator pos( settingsMap_.find( name ) );
  if( pos != settingsMap_.end() )
  {
    return pos->second;
  }
  //Not part of the scope of this answer....
  throw std::invalid_argument( "No such setting..." );
}

Settings()
{
  settingsMap_["mybool"] = "1";
  settingsMap_["myint"] = "5";
  settingsMap_["myfloat"] = "43.2";
}

SettingsMap settingsMap_;
};

int main()
{
  Settings s;
  std::cout << s.as<bool>("mybool") << " "
    << s.as<int>("myint") << " "
    << s.as<float>("myfloat");

  return 0;
}

I've implemented something similar to this, but I've used boost::any as my mapped type, and I've read the actual type during the first parse, therefore ensuring that the stored type is correct. I've also used boost::lexical_cast instead of native istringstream, but I've omitted that for the purpose of proving the point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM