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

时间:2018-04-17 11:00:39

标签: c++ c++11 templates variadic-templates sfinae

我尝试使用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中的表达式取决于&#34;立即上下文&#34;,即InnerT,即使表达式的该部分始终求值为false。< / p>

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

3 个答案:

答案 0 :(得分:15)

不要尝试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

答案 1 :(得分:7)

从评论中扩展:

  

我的想法是向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

答案 2 :(得分:2)

  

我怀疑问题是enable_if条件不依赖于bar的参数,

完全。

  

我的想法是添加一个额外的模板参数(称之为DummyType)到bar,默认为例如DummyType = T1,然后检查std :: is_same

我通常会看到这个解决方案。

  

但是bar采用参数包的事实使得这不可能(或者它......?)

否,如果您在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"

但你可以解决这个问题,强调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; }
};

现在你不能再“劫持”bar()了。