使用不依赖于方法模板参数的 enable_if

Use a enable_if that does not depend on method's template parameter

我正在尝试使用 std::enable_if 和 SFINAE 完全基于 class 的模板参数来切换 class 模板方法的实现。示例:

#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() 应该根据 T1T2 是否为同一类型而表现不同。但是,此代码无法编译。 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 中的表达式依赖于 "immediate context",即 InnerT,即使表达式的那部分总是求值为 false.

看起来我可以使用它作为解决方法,但感觉真的很笨拙和丑陋。你如何解决这个问题"correctly"?我的一个想法是向 bar() 添加一个额外的模板参数(称之为 DummyType),默认为例如DummyType = T1,然后检查 std::is_same<DummyType, T2>,但 bar() 采用参数包这一事实使得这不可能(或者它……?)

与其尝试将 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");
}

See it live

I suspect the problem is that the enable_if condition does not depend on the parameters of bar,

完全正确。

A thought I had was to add an additional template parameter (call it DummyType) to bar, which defaults to e.g. DummyType = T1, and then check std::is_same

我经常看到这个解决方案。

but the fact that bar takes a parameter pack makes this impossible (or does it…?)

否,如果您将 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; }

这非常有效。

此解决方案的缺点是您可以 "hijack" bar() 解释 D1 类型

Foo<int, int> f;

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

但你可以解决这个问题,强加 T1D1

相同
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; }
};

现在你不能 "hijack" bar()了。

从评论展开:

A thought I had was to add an additional template parameter (call it DummyType) to bar(), which defaults to e.g. DummyType = T1, and then check std::is_same<DummyType, T2>, but the fact that bar() takes a parameter pack makes this impossible (or does it…?)

没有。完全按照您的猜测行事,行不通。

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

But if I add the DummyType as second template parameter, and then later pass a list of template arguments that should go into the pack - how does the compiler now that the second argument should not go into DummyType, but be the first thing that's part of Args?

这就是我添加为最后一个参数的原因。如果模板非包参数有默认值,则允许它们跟在模板包参数之后。编译器将为 Args 使用所有显式指定的参数,并且无论您指定哪个参数,都将使用 DummyType = T1