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

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

本文关键字:参数 enable if 依赖于 方法      更新时间:2023-10-16

>我正在尝试使用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()的行为应该根据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内的表达式依赖于"直接上下文",即InnerT,即使表达式的那部分总是计算为false

看起来我可以将其用作解决方法,但这感觉真的很笨拙和丑陋。你如何"正确"解决这个问题?我有一个想法是添加一个额外的模板参数(称之为DummyType)到bar(),默认为 例如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");
}

现场观看

要从注释中展开:

我有一个想法是在bar()中添加一个额外的模板参数(称之为DummyType),默认为 例如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

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

完全。

我有一个想法是添加一个额外的模板参数(称之为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; }

这非常有效。

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

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()了。