我正在努力升级一些C ++代码以利用C ++ 11中的新功能。我有一个特性类,其中有一些函数返回基本类型,这些函数大部分时间(但并不总是)返回一个常量表达式。我想根据函数是否为constexpr
来做不同的事情。我提出了以下方法:
template<typename Trait>
struct test
{
template<int Value = Trait::f()>
static std::true_type do_call(int){ return std::true_type(); }
static std::false_type do_call(...){ return std::false_type(); }
static bool call(){ return do_call(0); }
};
struct trait
{
static int f(){ return 15; }
};
struct ctrait
{
static constexpr int f(){ return 20; }
};
int main()
{
std::cout << "regular: " << test<trait>::call() << std::endl;
std::cout << "constexpr: " << test<ctrait>::call() << std::endl;
}
额外的int
/ ...
参数是这样的,如果两个函数在 SFINAE 之后可用,则第一个函数会通过重载分辨率来选择。
使用 Clang 3.2 编译并运行它会显示:
regular: 0
constexpr: 1
所以这似乎有效,但我想知道代码是否合法C ++ 11。特别是因为我的理解是 SFINAE 的规则已经改变了。
答案 0 :(得分:13)
注意: I opened a question here关于OP代码是否实际有效。我在下面重写的例子无论如何都适用。
但我想知道代码是否合法C ++ 11
虽然默认模板参数可能被认为有点不寻常。我个人更喜欢以下风格,这类似于你(阅读:I)将特征写入check for a function's existence,只使用非类型模板参数并省略decltype
:
#include <type_traits>
namespace detail{
template<int> struct sfinae_true : std::true_type{};
template<class T>
sfinae_true<(T::f(), 0)> check(int);
template<class>
std::false_type check(...);
} // detail::
template<class T>
struct has_constexpr_f : decltype(detail::check<T>(0)){};
解释时间〜
您的原始代码是†,因为默认模板参数的实例化点是其功能模板的实例化点,在您的情况下,在main
中,因此它可以'早于那个被替换。
§14.6.4.1 [temp.point] p2
如果以一种使用该函数模板的默认参数定义的方式调用函数模板[...],则默认参数的实例化点是实例化的实例。功能模板[...]。
之后,这通常是SFINAE规则。
†至少我认为如此,标准中并没有完全明确。
答案 1 :(得分:1)
由@ marshall-clow提示,我将一个更为通用的类型特征版本用于检测constexpr
。我在std::invoke_result
上对其进行了建模,但由于constexpr
取决于输入,因此模板参数用于传入的值,而不是类型。
它有点受限,因为模板args只能是limited set of types,并且当它们到达方法调用时它们都是 const 。如果需要其他类型,可以轻松测试constexpr
包装器方法,或者参考参数可以非常数左值。
更多的是练习和演示,而不是实际有用的代码。
使用template<auto F, auto... Args>
使其成为仅限C ++ 17,需要gcc 7或clang 4. MSVC 14.10.25017无法编译它。
namespace constexpr_traits {
namespace detail {
// Call the provided method with the provided args.
// This gives us a non-type template parameter for void-returning F.
// This wouldn't be needed if "auto = F(Args...)" was a valid template
// parameter for void-returning F.
template<auto F, auto... Args>
constexpr void* constexpr_caller() {
F(Args...);
return nullptr;
}
// Takes a parameter with elipsis conversion, so will never be selected
// when another viable overload is present
template<auto F, auto... Args>
constexpr bool is_constexpr(...) { return false; }
// Fails substitution if constexpr_caller<F, Args...>() can't be
// called in constexpr context
template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
constexpr bool is_constexpr(int) { return true; }
}
template<auto F, auto... Args>
struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};
}