enable_if: минимальный пример для функции-члена void без аргументов

Я пытаюсь лучше понять std::enable_if в C ++ 11 и пытаюсь написать минимальный пример: класс A с функцией-членом void foo(), которая имеет разные реализации на основе типа T из шаблона класса.
Приведенный ниже код дает желаемый результат, но я еще не полностью его понимаю. Почему работает версия V2, а V1 нет? Почему требуется "избыточный" тип U?

#include <iostream>
#include <type_traits>

template <typename T>
class A {

    public:

        A(T x) : a_(x) {}

        // Enable this function if T == int
        /* V1 */ // template <           typename std::enable_if<std::is_same<T,int>::value,int>::type = 0>
        /* V2 */ template <typename U=T, typename std::enable_if<std::is_same<U,int>::value,int>::type = 0>
        void foo() { std::cout << "\nINT: " << a_ << "\n"; }

        // Enable this function if T == double
        template <typename U=T, typename std::enable_if<std::is_same<U,double>::value,int>::type = 0>
        void foo() { std::cout << "\nDOUBLE: " << a_ << "\n"; }

    private:

        T a_;

};

int main() {
    A<int> aInt(1); aInt.foo();
    A<double> aDouble(3.14); aDouble.foo();
    return 0;
}

Есть ли лучший способ достичь желаемого результата, т.е. иметь разные реализации функции void foo() на основе параметра шаблона класса?


person mattu    schedule 16.01.2017    source источник
comment
В вашем примере не подходит enable_if. Простая перегрузка решит ваш случай. enable_if в основном полезен для выведенного параметра шаблона.   -  person Kerrek SB    schedule 16.01.2017
comment
Использование std::enable_if подходит для отделения выведенных типов с плавающей запятой, скажем, от целочисленных типов. Два конкретных типа, подобных этому, лучше подходят для перегрузки.   -  person WhozCraig    schedule 16.01.2017
comment
@KerrekSB @WhozCraig Как мне перегрузить в этом конкретном случае? Используя внеклассное определение void A<int>::foo() {} и void A<double>::foo() {}? Я хотел бы, чтобы окончательный код содержал только те версии функции, которые требуются (то есть без функции A<double>::foo(), если такая функция никогда не вызывается)   -  person mattu    schedule 16.01.2017
comment
Насколько я понимаю, вопрос в том, почему и как избыточный typename U=T заставляет этот пример работать, но не без этого. Кто-нибудь объяснит это?   -  person A.S.H    schedule 16.01.2017


Ответы (2)


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

Вы можете заменить свои функции-члены foo следующими и иметь идентичную функциональность:

template<typename U=T> typename std::enable_if<std::is_same<U,int>::value>::type
foo(){ /* enabled when T is type int */ }

template<typename U=T> typename std::enable_if<std::is_same<U,double>::value>::type
foo(){ /* enabled when T is type double */ }

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

person Jeffrey Cash    schedule 19.01.2017
comment
Я думаю, вам также нужно удалить void перед функцией, поскольку теперь он уже включен в std::enable_if<>. Но спасибо за этот вклад, помог мне приблизиться на один шаг к пониманию того, что происходит. Вы бы сказали, что это законный способ достижения полиморфизма во время компиляции? - person mattu; 20.01.2017
comment
@untergam Ой, хороший улов! Я отредактирую это сейчас. Я думаю, что это законный способ сделать что-то, я просто не уверен, что есть способы сделать это лучше - person Jeffrey Cash; 20.01.2017

Что касается первого вопроса: почему V1 не работает? SFINAE применяется только при разрешении перегрузки - V1, однако, вызывает ошибку в точке, где создается экземпляр типа A, задолго до разрешения перегрузки foo().

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

template <typename T>
class A_Helper;

template <>
class A_Helper<int> {
public:
    static void foo( int value ){
        std::cout << "INT: " << value << std::endl;
    }
};

template <>
class A_Helper<double> {
public:
    static void foo( double value ){
        std::cout << "DOUBLE: " << value << std::endl;
    }
};

template <typename T>
class A {
public:

    A( T a ) : a_(a) 
    {}

    void foo(){
        A_Helper<T>::foo(a_);
    }

private:
    T a_;
};

Остальная часть A может быть объявлена ​​только один раз обычным способом - только те части, которые отличаются друг от друга, передаются помощнику. Есть много возможных вариантов - в зависимости от ваших требований ...

person j_kubik    schedule 16.01.2017
comment
Спасибо @j_kubik за ваш ответ. Я вижу, как это решение с шаблонными вспомогательными классами будет работать, но это именно тот вид раздувания кода, которого я пытался избежать в первую очередь. Я бы предпочел минимальную версию с enable_if или хотя бы с таким же коротким синтаксисом. - person mattu; 18.01.2017