Специализация шаблона оператора преобразования - PullRequest
20 голосов
/ 12 октября 2011

Это академическое упражнение по пониманию операторов преобразования, шаблонов и шаблонных специализаций.Шаблон оператора преобразования в следующем коде работает для int, float и double, но завершается ошибкой при использовании с std::string ... своего рода.Я создал специализацию преобразования в std::string, которая работает при использовании с инициализацией std::string s = a;, но не работает при использовании с приведением static_cast<std::string>(a).

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

class MyClass {
     int y;
public:
    MyClass(int v) : y(v) {}
    template <typename T>
    operator T() { return y; };
};

template<>
MyClass::operator std::string() {
    std::stringstream ss;
    ss << y << " bottles of beer.";
    return ss.str();
}

int main () {
    MyClass a(99);
    int i    = a;
    float f  = a;
    double d = a;
    std::string s = a;

    std::cerr << static_cast<int>(a) << std::endl;
    std::cerr << static_cast<float>(a) << std::endl;
    std::cerr << static_cast<double>(a) << std::endl;
    std::cerr << static_cast<std::string>(a) << std::endl; // Compiler error
}

Приведенный выше код генерирует компиляторошибка в g ++ и icc, оба жалуются, что пользовательское преобразование не подходит для преобразования экземпляра MyClass в std::string на static_cast (приведение в стиле C ведет себя одинаково).

ЕслиЯ заменяю приведенный выше код явными, не шаблонными версиями оператора преобразования, все устраивает:

class MyClass {
    int y;
public:
    MyClass(int v) : y(v) {}
    operator double() {return y;}
    operator float()  {return y;}
    operator int()    {return y;}
    operator std::string() {
        std::stringstream ss;
        ss << y << " bottles of beer.";
        return ss.str();
    }
};

Что не так с моей специализацией по шаблону для std::string?Почему это работает для инициализации, но не для приведения?

Обновление:

После некоторого шаблонаного мастерства от @ luc-danton (трюки метапрограммирования, которые я никогда раньше не видел), у меня есть следующеекод работает в g ​​++ 4.4.5 после включения экспериментальных расширений C ++ 0x.Помимо ужаса того, что делается здесь, требование экспериментальных опций компилятора является достаточной причиной, чтобы , а не сделать это.Несмотря на это, мы надеемся, что это так же полезно для других, как и для меня:

class MyClass {
    int y;
public:
    MyClass(int v) : y(v) {}

    operator std::string() { return "nobody"; }

    template <
        typename T
        , typename Decayed = typename std::decay<T>::type
        , typename NotUsed = typename std::enable_if<
            !std::is_same<const char*, Decayed>::value &&
            !std::is_same<std::allocator<char>, Decayed>::value &&
            !std::is_same<std::initializer_list<char>, Decayed>::value
          >::type
    >
    operator T() { return y; }
};

Это, очевидно, заставляет компилятор выбирать преобразование operator std::string() для std::string, которое преодолевает любую неопределенность, с которой сталкивался компилятор.

Ответы [ 2 ]

9 голосов
/ 12 октября 2011

Вы можете воспроизвести проблему, используя

std::string t(a);

В сочетании с фактической ошибкой из GCC (error: call of overloaded 'basic_string(MyClass&)' is ambiguous) у нас есть четкие подсказки относительно того, что может происходить: есть одна предпочтительная последовательность преобразования в случае копирования инициализации (std::string s = a;), и в случае прямой инициализации (std::string t(a); и static_cast) существует как минимум две последовательности, где одна из них не может быть предпочтительнее другой.

Рассматривая все явные конструкторы std::basic_string, принимающие один аргумент (единственные, которые будут учитываться при прямой инициализации, но не при инициализации копирования), мы находим explicit basic_string(const Allocator& a = Allocator());, который на самом деле является единственным явным конструктором.

К сожалению, я не могу сделать ничего, кроме этой диагностики: я не могу придумать трюк, чтобы обнаружить, является ли operator std::allocator<char> экземпляром или нет (я пробовал SFINAE и operator std::allocator<char>() = delete;, но безуспешно), и я тоже знаю немного о специализациях шаблонов функций, разрешении перегрузки и требованиях к библиотеке, чтобы узнать, соответствует ли поведение GCC или нет.

Поскольку вы говорите, что упражнение носит академический характер, я избавлю вас от обычной диатрибы о том, что неявные операторы преобразования не являются хорошей идеей. Я думаю, ваш код является достаточно хорошим примером того, почему, так или иначе:)


Я заставил СФИНА работать. Если оператор объявлен как:

template <
    typename T
    , typename Decayed = typename std::decay<T>::type
    , typename = typename std::enable_if<
        !std::is_same<
            const char*
            , Decayed
        >::value
        && !std::is_same<
            std::allocator<char>
            , Decayed
        >::value
        && !std::is_same<
            std::initializer_list<char>
            , Decayed
        >::value
    >::type
>
operator T();

Тогда двусмысленности нет, и код скомпилируется, будет выбрана специализация для std::string, и результирующая программа будет вести себя как нужно. У меня до сих пор нет объяснения, почему инициализация копирования хороша.

1 голос
/ 12 октября 2011

static_cast здесь эквивалентно выполнению std::string(a).

Обратите внимание, что std::string s = std::string(a); также не компилируется.Я предполагаю, что существует множество перегрузок для конструктора, и версия шаблона может преобразовать a во многие подходящие типы.

С другой стороны, с фиксированным списком преобразований, только одно из этих совпаденийименно тот тип, который принимает конструктор строки.

Чтобы проверить это, добавьте преобразование в const char* - версия без шаблонов должна начать сбой в том же месте.

(Теперь вопросВот почему std::string s = a; работает. Тонкие различия между этим и std::string s = std::string(a); известны только богам.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...