使用不依赖于方法模板参数的 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()
应该根据 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
中的表达式依赖于 "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");
}
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"
但你可以解决这个问题,强加 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; }
};
现在你不能 "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
。
我正在尝试使用 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()
应该根据 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
中的表达式依赖于 "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");
}
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"
但你可以解决这个问题,强加 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; }
};
现在你不能 "hijack" bar()
了。
从评论展开:
A thought I had was to add an additional template parameter (call it
DummyType
) tobar()
, which defaults to e.g.DummyType = T1
, and then checkstd::is_same<DummyType, T2>
, but the fact thatbar()
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
。