1

I'm still trying to update my older programs to modern C++, and I have another question related to templates. I'm using libconfig++ to handle program configuration, writing plain text data to a configuration file. You can choose the type of data you want to write to the file, and the library uses an enum to do this (ie Setting::TypeString, Setting::TypeBoolean, etc.)

At first, I used plain overloading as you can see below :

void Config::write_value(Setting& root, const string& key, const string& value) {
    if (!root.exists(key.c_str())) root.add(key.c_str(), Setting::TypeString) = value;
    else {
        Setting& s = root[key.c_str()];
        s = value;
    }
}

void Config::write_value(Setting& root, const string& key, const bool value) {
    if (!root.exists(key.c_str())) root.add(key.c_str(), Setting::TypeBoolean) = value;
    else {
        Setting& s = root[key.c_str()];
        s = value;
    }
}

But these functions are candidates for a generic treatment, as just one parameter changes, and the type used by the library can be deduced from the parameter.

The question is : how can I do it ? I tried using conditional_t and is_same, but the compiler isn't happy about what I tried, something's wrong and I think it's related to the enum ...

What's the right way to map the enum to the parameter type ?

Here's one (of numerous) thing I tried :

template<typename T>
using Type = std::conditional_t<std::is_same_v<T, bool>, Setting::TypeBoolean, void>;

template<typename T>
void write_value(libconfig::Setting& root, const std::string& key, const T& value)
{
    if (!root.exists(key.c_str())) root.add(key.c_str(), Type<T>) = value;
    else {
        Setting& s = root[key.c_str()];
        s = value;
    }
}

// Compiler error :
error C2275: 'Config::Type<bool>' :  illegal use of this type as an expression

Thanks for reading me :)

Edit Setting::* is an enum, defined as Setting::Type actually.

 enum Type
  {
    TypeNone = 0,
    // scalar types
    TypeInt,
    TypeInt64,
    TypeFloat,
    TypeString,
    TypeBoolean,
    // aggregate types
    TypeGroup,
    TypeArray,
    TypeList
  };
3
  • Can/are you using C++17? Commented Jun 19, 2018 at 16:28
  • Setting::* is not a type, since it is passed as an argument to the function. What is it? Commented Jun 19, 2018 at 16:30
  • Yes, I'm using the latest Visual Studio, so I guess a majority of C++17 is available. Commented Jun 19, 2018 at 16:31

2 Answers 2

2

You don't explain here how the object root.add() is able to assign from an arbitrary type, but that does not appear to be the problem you are currently asking us to solve...

The problem you are currently tackling is specifically translating the template type to the appropriate enum index, and for that in c++11 I would simply define a translator class:

template <class T> class ToMySetting {};
template <> ToMySetting<int> { static Setting index = {Setting::TypeInt}; };    
template <> ToMySetting<char*> { static Setting index = {Setting::TypeCStr}; };    
template <> ToMySetting<bool> { static Setting index = {Setting::TypeBool}; };

You can then use that from your write_value function as ToMySetting<T>::index

In newer versions (c++17) I would tend to use if constexpr syntax to define the ToMySetting translator class body just once.

Sign up to request clarification or add additional context in comments.

1 Comment

Actually, add() is part of the library, I just use it without knowing what's under the hood ... I tried your solution, but I think there's something amiss as I can't get it to work, compiler returns **error C2988: unrecognizable template declaration/definition ** ... Could you give your example using if constexpr, for my education please ?
0

After some research based on @gem-taylor answer, I ended up with the following version which is working as I wanted. It still needs some tweaking to include std::string version, but it's not essential.

template <class T> struct ToMySetting;
template <> struct ToMySetting<bool>        { static constexpr libconfig::Setting::Type index = libconfig::Setting::TypeBoolean; };
template <> struct ToMySetting<uint32_t>    { static constexpr libconfig::Setting::Type index = libconfig::Setting::TypeInt; };
template <> struct ToMySetting<uint64_t>    { static constexpr libconfig::Setting::Type index = libconfig::Setting::TypeInt64; };
template <> struct ToMySetting<float>       { static constexpr libconfig::Setting::Type index = libconfig::Setting::TypeFloat; };
template <> struct ToMySetting<const char*> { static constexpr libconfig::Setting::Type index = libconfig::Setting::TypeString; };

template<class T>
void write_value(libconfig::Setting& root, const std::string& key, const T& value)
{
    if (!root.exists(key.c_str())) root.add(key.c_str(), ToMySetting<T>::index) = value;
    else {
        libconfig::Setting& s = root[key.c_str()];
        s = value;
    }
}

Edit: for completeness, here are the 2 specializations for char array and std::string :

void write_value(libconfig::Setting& root, const std::string& key, const std::string& value)
{
    if (!root.exists(key.c_str())) root.add(key.c_str(), libconfig::Setting::TypeString) = value.c_str();
    else {
        libconfig::Setting& s = root[key.c_str()];
        s = value.c_str();
    }
}

template<size_t N>
void write_value(libconfig::Setting& root, const std::string& key, const char (&value)[N])
{
    if (!root.exists(key.c_str())) root.add(key.c_str(), libconfig::Setting::TypeString) = value;
    else {
        libconfig::Setting& s = root[key.c_str()];
        s = value;
    }
}

1 Comment

Cool! :-) Sorry it was only a hint, rather than complete. I'm sure there is a solution using std::is_same_v, but the translator class is simpler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.