简体   繁体   中英

How to prevent std::string from using the initializer_list constructor?

I want the following code to output "test" instead of "X" for the case when using std::string using the same initialization as the other basic types. std::string now calls the constructor with an initializer_list and therefore the template specialization of get for char is called.

#include <sstream>
#include <string>
#include <iostream>

// Imagine this part as some kind of cool parser.
// I've thrown out everything for a simpler demonstration.
template<typename T> T get() {}
template<> int get(){ return 5; }
template<> double get(){ return .5; }
template<> char get(){ return 'X'; }
template<> std::string get(){ return "test"; }

struct Config {
    struct proxy {
        // use cool parser to actually read values
        template<typename T> operator T(){ return get<T>(); }
    };

    proxy operator[](const std::string &what){ return proxy{}; }
};

int main()
{
    auto conf = Config{};

    auto nbr = int{ conf["int"] };
    auto dbl = double{ conf["dbl"] };
    auto str = std::string{ conf["str"] };

    std::cout << nbr << std::endl; // 5
    std::cout << dbl << std::endl; // 0.5
    std::cout << str << std::endl; // 'X'
}

Is there a nice way of doing this without breaking the consistent look of the variable initializations?

std::string has a constructor that takes an initializer_list<char> argument; that constructor will always be considered first when you use list-initialization with a non-empty braced-init-list, that's why the char specialization of get() is being matched.

If you use parentheses instead of braces for all the initializations, the initializer_list constructor will no longer be the only one considered in the std::string case.

auto nbr = int( conf["int"] );
auto dbl = double( conf["dbl"] );
auto str = std::string( conf["str"] );

However, this change alone doesn't work because you have an implicit user-defined conversion template that can yield any type. The code above, in the std::string case, results in matches for all std::string constructors that can be called with a single argument. To fix this make the conversion operator explicit .

struct proxy {
    // use cool parser to actually read values
    template<typename T>
    explicit operator T(){ return get<T>(); }
};

Now, only the explicit conversion to std::string is viable, and the code works the way you want it to.

Live demo

auto nbr = (int)conf["int"];
auto dbl = (double)conf["dbl"];
auto str = (string&&)conf["str"];

you have defined template operator T(), the above just calls it. to make a copy, you can

auto str = string((string&&)conf["str"])

EDIT: changed (string) to (string&&)

EDIT2: following works as well (tested them all - gcc -std=c++11):

auto nbr = (int&&)conf["int"];
auto dbl = (double&&)conf["dbl"];
auto str = (string&&)conf["str"];

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