Используйте параметр enable_if, который не зависит от параметра шаблона метода

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

#include <type_traits>

template<class T1, class T2>
class Foo {
    template<class InnerT, class ... Args>
    typename std::enable_if<std::is_same<T1, T2>::value, void>::type
    bar(InnerT param) {};

    template<class InnerT, class ... Args>
    typename std::enable_if<!std::is_same<T1, T2>::value, void>::type
    bar(InnerT param) {};
};


int main() {
    Foo<int, int> f;
}

Здесь bar() должен вести себя по-разному в зависимости от того, являются ли T1 и T2 одинаковыми или нет. Однако этот код не компилируется. Ни GCC, ни clang не говорят мне ничего полезного. Я подозреваю, что проблема заключается в том, что условие std::enable_if не зависит от параметров bar(), то есть не от его непосредственного контекста, как указано в пункте 17.8.2, пункт 8 стандарта.

Это предположение подтверждается тем фактом, что этот код компилируется отлично:

#include <type_traits>

class DummyClass {};

template<class T1, class T2>
class Foo {
    template<class InnerT, class ... Args>
    typename std::enable_if<std::is_same<T1, T2>::value || 
                            std::is_same<InnerT, DummyClass>::value, void>::type
    bar(InnerT param) {};

    template<class InnerT, class ... Args>
    typename std::enable_if<!std::is_same<T1, T2>::value || 
                            std::is_same<InnerT, DummyClass>::value, void>::type
    bar(InnerT param) {};
};


int main() {
    Foo<int, int> f;
}

Теперь выражение внутри std::enable_if зависит от "непосредственного контекста", а именно от InnerT, хотя эта часть выражения всегда оценивается как false.

Похоже, я могу использовать это в качестве обходного пути, но это кажется очень хакированным и уродливым. Как вы решаете эту проблему "правильно"? Я думал, что нужно добавить дополнительный параметр шаблона (назовем его DummyType) в bar(), который по умолчанию имеет значение, например, DummyType = T1, а затем проверить std::is_same<DummyType, T2>, но тот факт, что bar() принимает пакет параметров делает это невозможным (или делает это??)

Ответ 1

Вместо того, чтобы пытаться использовать SFINAE в двух реализациях, просто используйте нормальное разрешение перегрузки.

#include <type_traits>
#include <iostream>

template<class T1, class T2>
class Foo {
    template<class InnerT, class ... Args>
    void do_bar(InnerT param, std::true_type, Args... args) { std::cout << "same" << std::endl; }

    template<class InnerT, class ... Args>
    void do_bar(InnerT param, std::false_type, Args... args) { std::cout << "not same" << std::endl; }

public:
    template<class InnerT, class ... Args>
    void bar(InnerT&& param, Args&&... args) 
    {
        do_bar(std::forward<InnerT>(param), std::is_same<T1, T2>{}, std::forward<Args>(args)...);
    }

};

int main() {
    Foo<int, int> f1;
    Foo<int, double> f2;

    f1.bar(1, 2, 3);
    f2.bar("Hello");
}

Смотрите онлайн

Ответ 2

Чтобы перейти от комментариев:

Я думал, что нужно добавить дополнительный параметр шаблона (назовем его DummyType) в bar(), который по умолчанию имеет значение, например, DummyType = T1, а затем проверить std::is_same<DummyType, T2>, но тот факт, что bar() принимает пакет параметров делает это невозможным (или делает это??)

Это не так. Выполнение того, что вы догадались, не сработает, будет работать.

#include <type_traits>

template<class T1, class T2>
struct Foo {
    template<class InnerT, class ... Args, class DummyType = T1>
    typename std::enable_if<std::is_same<DummyType, T2>::value, void>::type
    bar(InnerT param) {};

    template<class InnerT, class ... Args, class DummyType = T1>
    typename std::enable_if<!std::is_same<DummyType, T2>::value, void>::type
    bar(InnerT param) {};
};


int main() {
    Foo<int, int> f;
    f.bar(3);                   // InnerT = int; Args = empty; DummyType = int.
    f.bar<int, void, short>(4); // InnerT = int; Args = void, short; DummyType = int.
}

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

Вот почему я добавил в качестве последнего параметра. Параметры шаблона, не входящие в пакет, могут следовать за параметрами пакета шаблонов, если они имеют значение по умолчанию. Компилятор будет использовать все явно заданные аргументы для Args и будет использовать DummyType = T1 независимо от того, какие аргументы вы укажете.

Ответ 3

Я подозреваю, что проблема заключается в том, что условие enable_if не зависит от параметров bar,

Именно так.

Я думал, что нужно добавить дополнительный шаблонный параметр (назовем его DummyType) в bar, который по умолчанию имеет значение, например DummyType = T1, а затем проверить std :: is_same

Я обычно вижу именно это решение.

но тот факт, что бар принимает пакет параметров, делает это невозможным (или это...?)

Нет, если вы помещаете DummyType перед InnerT

   template <typename D1 = T1, typename InnerT, typename ... Args>
   typename std::enable_if<std::is_same<D1, T2>::value>::type
   bar (InnerT param) { std::cout << "- true version" << std::endl; }

   template <typename D1 = T1, typename InnerT, typename ... Args>
   typename std::enable_if<!std::is_same<D1, D2>::value>::type
   bar (InnerT param) { std::cout << "- false version" << std::endl; }

Это прекрасно работает.

Недостатком этого решения является то, что вы можете "захватить" bar() объясняя тип D1

Foo<int, int> f;

f.bar(0);        // print "- true version"
f.bar<long>(0);  // print "- false version"

но вы можете решить эту проблему, указав, что T1 такой же, как D1

template <typename T1, typename T2>
struct Foo {
   template <typename D1 = T1, typename InnerT, typename ... Args>
   typename std::enable_if<   std::is_same<D1, T2>::value
                           && std::is_same<D1, T1>::value>::type
   bar (InnerT param) { std::cout << "- true version" << std::endl; }

   template <typename D1 = T1, typename InnerT, typename ... Args>
   typename std::enable_if< ! std::is_same<D1, T2>::value
                           && std::is_same<D1, T1>::value>::type
   bar (InnerT param) { std::cout << "- false version" << std::endl; }
};

Теперь вы больше не можете "убирать" bar().