如果特定表达式格式不正确,很容易使用SFINAE来隐藏特定的函数重载。但是我想做相反的事情,当且仅当给定的表达式 格式良好时才隐藏过载,并且以非常一般的方式这样做。我有一个在clang 3.5.0和gcc 5.2.0中工作的解决方案,但我对任何评论和替代方案感兴趣。
理想情况下,会有一个内置的constexpr bool
函数/宏在编译时告诉我们特定表达式是否格式正确。
IS_WELL_FORMED( declval<T>() < declval<T>() ) // I want this as bool
可与enable_if
一起使用以启用或禁用重载。
我找到了一个解决方案,但是我在g ++ 5.2.0和clang 3.5.0中遇到了一些奇怪的行为,我想知道是否有错误。
首先,我到目前为止找到的最强大的解决方案,适用于两个编译器。例如,我想测试T
是否有.length()
方法。这需要将表达式“隐藏”在另一个模板中。另外,我将在稍后讨论一个名为well_formed_default
的函数。
// Define a template to contain our expression
template<typename T2=T, typename =
decltype( declval<T2>().length() ) // This line is the expression to test
> struct test_for_length_method { };
以下是它在包含类中的使用方法:
template<typename T>
struct Demo { // the main struct I'm working on
// Define a template to "hide" our expression
template<typename T2=T, typename =
decltype( declval<T2>().length() ) // This line is the expression to test
> struct test_for_length_method { };
// test if the above "test" succeeds
constexpr bool T_has_length =
well_formed_default< test_for_length_method >();
// demonstrate the bool in an enable_if
template<bool b = T_has_length>
static
auto demo_enable_if() -> typename std::enable_if< b >::type {
cout << "T has length" << endl;
}
template<bool b = T_has_length>
static
auto demo_enable_if() -> typename std::enable_if< !b >::type {
cout << "T doesn't" << endl;
}
}
上述内容与Demo<int>::demo_enable_if()
和Demo<std::string>::demo_enable_if()
一样正常工作。
我无法在T_has_length
内直接使用enable_if
因为它不会导致硬错误,因为它不是模板替换。因此,我通过在另一个模板参数bool b = T_has_length
中复制if来假装它是模板参数。这类似于我们在测试结构中使用typename T2=T
的方式。有点烦人,但我想这很有意义。
我现在定义well_formed_default
。它需要一个模板(以及可选的某些类型)并返回true或false,具体取决于它是否可以使用这些特定的args构造模板。我会在我的所有项目中自动包含它。也许这样的事情已经存在于标准中了吗?
template<template<class...> class Template, class ...Args>
constexpr auto well_formed_default_impl(int)
-> typename std::conditional<false,Template<Args...>, bool>::type {
return true;
}
template<template<class...> class Template, class ...Args>
constexpr auto well_formed_default_impl(...)
-> bool {
return false;
}
template<template<class...> class Template, class ...Args>
constexpr bool well_formed_default() {
return well_formed_default_impl<Template,Args...>(0);
}
-
这适用于g ++ 5.2.0和clang 3.5.0。但是应该吗?这是完全标准的,还是我推动标准太过分了?我想对我来说最奇怪的是在Template<Args...>
内使用well_formed_default_impl
- 这是否保证在我使用它的方式中取代失败?例如如果相关的test_for_pushback_method_struct<>
格式不正确,请decltype
-
(对于这个问题的其余部分,可能有助于查看this code on Coliru的输出,因为它包含我在下面讨论的所有测试和结果。)
我用别名而不是上面的结构开始了这个项目。我认为这是等价的。但相反,对于两个编译器,他们认为string
不有长度方法。
template<typename T2=T>
using test_for_length_method_alias = decltype( declval<T2>().length() );
最后,我尝试了结构和别名,但我明确定义了第一个类型参数(T
),而不是依赖于默认的T2=T
。这不应该改变任何东西,因为我传递的是默认类型 - 但它确实改变了行为!使用带有显式第一个参数的结构
well_formed_default<test_for_length_method_struct , T>
在两个编译器上都能正常工作。但alias-with-explicit-first-type只适用于clang:
well_formed_default<test_for_length_method_alias , T>
答案 0 :(得分:4)
对于直接支持这种用途的语言,不太可能有任何改变(除了概念带来的)。概念将使编写互斥的重载变得更容易,而否定的概念允许编写重载 如果表达式格式不正确,则启用。 (是的,我知道这不能帮助你为C ++ 11或C ++ 14编写这样的代码。)例如:
#include <iostream>
template <class T>
requires !requires(T t) {t.f();}
void f()
{
std::cout << "does not support f()!" << std::endl;
}
template <class T>
void f()
{
std::cout << "supports f()!" << std::endl;
}
struct A {};
struct B {void f() {}};
int main()
{
f<A>();
f<B>();
}
我添加了无约束的重载仅用于说明目的。 离开它会使调用f&lt; B&gt;()形成错误。该代码今天在gcc trunk上运行,您可以在melpon.org/wandbox上试用。
答案 1 :(得分:3)
这是can_apply
样板:
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
namespace details {
template<template<class...>class Z, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;
对于x<y
使用它的特定问题:
template<class Lhs, class Rhs>
using less_r = decltype( std::declval<Lhs>() < std::declval<Rhs>() );
template<class Lhs, class Rhs>
using can_less = can_apply< less_r, Lhs, Rhs >;
然后使用can_less
:
struct Foo {};
struct Bar {};
void operator<( Foo, Bar ) {}
int main() {
std::cout << can_less<Foo, Bar>{} << can_less<Bar,Foo>{} << can_less<int, int>{} << can_less<char*, int>{} << '\n';
}
在clang和gcc中输出1010
。
这符合标准,它大致匹配std::experimental::is_detected
的界面(功能较少,但实现起来更简单)。