Как предотвратить использование std::string конструктором initializer_list?

Я хочу, чтобы следующий код выводил "test" вместо "X" для случая при использовании std::string используя ту же инициализацию, что и другие основные типы. std::string теперь вызывает конструктор с initializer_list и, следовательно, специализация шаблона get за char называется.

#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'
}

Есть ли хороший способ сделать это, не нарушая согласованный вид инициализации переменных?

2 ответа

Решение

std::string имеет конструктор, который принимает initializer_list<char> аргумент; этот конструктор всегда будет учитываться первым, когда вы используете list-initialization с непустым braced-init-list, поэтому char специализация get() подбирается

Если вы используете скобки вместо скобок для всех инициализаций, initializer_list конструктор больше не будет единственным, рассмотренным в std::string дело.

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

Однако само по себе это изменение не работает, потому что у вас есть неявный пользовательский шаблон преобразования, который может давать любой тип. Код выше, в std::string случай, результаты в матчах для всех std::string конструкторы, которые могут быть вызваны с одним аргументом. Чтобы это исправить сделайте оператор преобразования explicit,

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

Теперь только явное преобразование в std::string является жизнеспособным, и код работает так, как вы хотите.

Живая демо

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

Вы определили шаблонный оператор T(), вышеприведенный просто вызывает его. сделать копию, вы можете

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

РЕДАКТИРОВАТЬ: изменено (строка) на (строка &&)

EDIT2: следующие работы также (проверили их все - gcc -std= C++11):

auto nbr = (int&&)conf["int"];
auto dbl = (double&&)conf["dbl"];
auto str = (string&&)conf["str"];
Другие вопросы по тегам