37

我正在升级一些 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的规则已经改变。

4

2 回答 2

14

注意: 我在这里提出了一个关于 OPs 代码是否真的有效的问题。我在下面重写的示例在任何情况下都可以使用。


但我想知道代码是否合法 C++11

它是,虽然默认模板参数可能被认为有点不寻常。我个人更喜欢以下样式,这类似于您(阅读:我)编写特征来检查函数的存在,只是使用非类型模板参数并省略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)){};

活生生的例子。


讲解时间~

您的原始代码有效†</sup> 因为默认模板参数的实例化点是其函数模板的实例化点,在您的情况下,意思是 in main,因此不能在此之前替换它。

§14.6.4.1 [temp.point] p2

如果以使用该函数模板的默认参数定义的方式调用函数模板 [...] [...],则默认参数的实例化点就是函数模板的实例化点 [... ...]。

在那之后,这只是通常的 SFINAE 规则。


†至少我认为是这样,标准中并不完全清楚。

于 2013-03-06T00:11:41.543 回答
2

在@marshall-clow 的提示下,我整理了一个更通用的 type-trait 版本来检测constexpr. 我对它进行了建模std::invoke_result,但是由于constexpr取决于输入,因此模板参数用于传入的值,而不是类型。

它有点有限,因为模板 args 只能是一组有限的 types ,并且当它们到达方法调用时它们都是const 。如果您需要其他类型或引用参数的非 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)> {};

}

使用 wandbox 上的用例进行现场演示

于 2017-04-30T00:16:46.603 回答