【问题标题】:Detecting constexpr with SFINAE使用 SFINAE 检测 constexpr
【发布时间】:2013-02-20 09:11:14
【问题描述】:

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

【问题讨论】:

  • 有趣的问题。我写了一个我认为证明它是合法的答案,但后来我写了一个modified version,根据我的答案应该同样有效,而且它不能在任何编译器上编译。所以我不打算提交答案,但我很好奇。
  • @AndyProwl:我很好奇,您能否在 liveworkspace.org 或其他类似网站上发布您的解决方案?
  • @K-ballo:如果你的意思是答案,是的。我把它贴在这里并删除它,你仍然可以看到它。
  • @andy 我认为您的想法错误是假设扣除期间的替换将替换为默认模板参数。但如果你仔细观察,你会发现默认模板参数已经被更高一级的类模板所取代。当实例化类模板特化的成员声明时,成员模板的默认模板参数不会立即实例化,而只会在使用时实例化。然而,这并不意味着它们在使用点的实例化是一个 SFINAE 上下文。

标签: c++ templates c++11 sfinae constexpr


【解决方案1】:

注意: I opened a question here 关于 OPs 代码是否实际有效。下面我重写的示例在任何情况下都可以工作。


但我想知道代码是否合法 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)){};

Live example.


说明时间~

您的原始代码有效 因为默认模板参数的实例化点是其函数模板的实例化点,这意味着,在您的情况下,在main,所以它不能比那个更早替换。

§14.6.4.1 [temp.point] p2

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

之后,就是通常的 SFINAE 规则。


† 至少我是这么认为的,标准中并没有完全明确。

【讨论】:

  • 该标准的引用消除了我所有的疑虑,谢谢。
  • 不幸的是,我认为这不是那么清楚。无法为 C++03 中的函数指定“默认模板参数”,但该文本也存在于那里。文本只讨论函数的默认参数,而不是类模板。这让我相信它不是在谈论模板参数,而是在谈论函数参数。
  • 我不同意这段代码显然是有效的,因为不清楚错误是否在即时上下文中,也不清楚是否发生延迟实例化。
  • @Johannes:哦,好点子。我有点怀疑 OP 的代码是否真的有效,直到我偶然发现了标准的那部分。介意和我一起去休息室讨论这个吗?
  • @xeo 我在上班路上的公交车上。当我还在打电话时,我写了我的 cmets。所以很遗憾我不能加入休息室:-)
【解决方案2】:

在@marshall-clow 的提示下,我整理了一个更通用的类型特征版本,用于检测constexpr。我以std::invoke_result 为模型,但因为constexpr 依赖于输入,所以模板参数用于传入的值,而不是类型。

它有点受限,因为模板参数只能是limited set of types,并且当它们到达方法调用时它们都是const。如果您需要其他类型,您可以轻松测试 constexpr 包装器方法,或 非 const 左值作为参考参数。

与其说是实际有用的代码,不如说是练习和演示。

并且使用template&lt;auto F, auto... Args&gt; 使其仅支持 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)> {};

}

Live demo with use-cases on wandbox

【讨论】:

  • template&lt;auto F, auto... Args, auto = constexpr_caller&lt;F, Args...&gt;()&gt; 怎么可能合法,因为第三个参数在省略号之后?
  • 来自cppreference: "在初级类模板中,模板参数包必须是模板参数列表中的最后一个参数。在函数模板中,模板参数包可能出现在列表的前面前提是所有以下参数都可以从函数参数中推导出来,或者具有默认参数”。基本上,包后的参数不能被实例化的参数填充,但如果它们被推导或默认,则允许。这仅适用于函数模板。
猜你喜欢
  • 1970-01-01
  • 2020-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-11
相关资源
最近更新 更多