实现is_constexpr_copiable

Her*_*ora 22 c++ templates language-lawyer constexpr c++17

我试图实现一个类似于std::is_constructible异常的值模板,只有当类型在constexpr环境中可复制时才是真的(即它的复制构造函数是constexpr限定的).我到达了以下代码:

#include <type_traits>

struct Foo {
    constexpr Foo() = default;
    constexpr Foo(const Foo&) = default;
};
struct Bar {
    constexpr Bar() = default;
    Bar(const Bar&);
};

namespace detail {
template <int> using Sink = std::true_type;
template<typename T> constexpr auto constexpr_copiable(int) -> Sink<(T(T()),0)>;
template<typename T> constexpr auto constexpr_copiable(...) -> std::false_type;
}
template<typename T> struct is_constexpr_copiable : decltype(detail::constexpr_copiable<T>(0)){ };

static_assert( is_constexpr_copiable<Foo>::value, "");
static_assert(!is_constexpr_copiable<Bar>::value, "");
Run Code Online (Sandbox Code Playgroud)

现在我问自己这是否符合标准,因为编译器似乎不同意输出. https://godbolt.org/g/Aaqoah


编辑(c ++ 17功能):

is_constexpr_constructible_from使用c ++ 17的新的自动非类型模板类型实现稍微不同的时候,我再次发现编译器之间的区别,当在constexpr表达式中取消引用nullptr时SFINAE.

#include <type_traits>

struct Foo {
    constexpr Foo() = default;
    constexpr Foo(const Foo&) = default;
    constexpr Foo(const Foo*f):Foo(*f) {};
};
struct Bar {
    constexpr Bar() = default;
    Bar(const Bar&);
};

namespace detail {
template <int> struct Sink { using type = std::true_type; };
template<typename T, auto... t> constexpr auto constexpr_constructible_from(int) -> typename Sink<(T(t...),0)>::type;
template<typename T, auto... t> constexpr auto constexpr_constructible_from(...) -> std::false_type;
}
template<typename T, auto... t> struct is_constexpr_constructible_from : decltype(detail::constexpr_constructible_from<T, t...>(0)){ };

constexpr Foo foo;
constexpr Bar bar;
static_assert( is_constexpr_constructible_from<Foo, &foo>::value, "");
static_assert(!is_constexpr_constructible_from<Foo, nullptr>::value, "");
static_assert(!is_constexpr_constructible_from<Bar, &bar>::value, "");

int main() {}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/g/830SCU


编辑:( 2018年4月)

既然两个编译器都支持C++ 17,我发现下面的代码工作得更好(不需要`T`上的默认构造函数),但仅限于clang.一切都仍然相同,但用以下内容替换命名空间`detail`:namespace detail {template struct Sink {}; 模板constexpr自动接收器(S) - > std :: true_type; 模板constexpr auto try_copy() - > Sink; template constexpr auto constexpr_copiable(int) - > decltype(sink(std :: declval,0)>>())); template constexpr auto constexpr_copiable(...) - > std :: false_type; } https://godbolt.org/g/3fB8jt这非常深入到关于未评估上下文的标准部分,并且两个编译器都拒绝允许用`const T&`替换`const T*`并使用`std :: declval( )`而不是`nullptr`-cast.如果我确认clang的行为是公认的标准化行为,我会将此版本提升到一个答案,因为它只需要完全要求的内容.

Clang nullptr在评估未评估的操作数时接受一些未定义的行为,取消引用decltype.

Her*_*ora 5

最艰难的挑战是给出一个函数来评估任意 T 的constexpr构造函数是否存在,这里给出的在 C++17 中似乎几乎不可能。const T&幸运的是,我们可以在没有这些的情况下走很长的路。其理由如下:

\n\n

了解问题空间

\n\n

以下限制对于确定是否可以在constexpr内容中评估某些表达式非常重要:

\n\n
    \n
  • 要计算 的复制构造函数,需要Ttype 的值。const T&这样的值必须引用具有活动生命周期的对象,即在constexpr上下文中它必须引用在逻辑封闭表达式中创建的某个值。

  • \n
  • 为了创建此引用作为任意临时提升的结果,T我们需要知道并调用一个构造函数,其参数实际上可能涉及任意其他表达式,constexpr我们需要评估其 -ness。constexpr据我所知,这看起来需要解决确定一般表达式的性质的一般问题。\xc2\xb9

  • \n
  • \xc2\xb9 实际上,如果任何带有参数的构造函数(包括复制构造函数)被定义为constexpr,则必须有某种有效的方法来构造T,无论是作为聚合初始化还是通过构造函数。否则,程序将是错误的,这可以通过constexpr说明符 \xc2\xa710.1.5.5的要求来确定:

    \n\n
    \n

    对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,则函数或构造函数的调用可以是核心常量表达式的计算子表达式,或者对于构造函数,可以是常量初始值设定项某些对象([basic.start.static]),程序格式错误,无需诊断。

    \n
    \n\n

    这可能会给我们带来一个微小的漏洞。\xc2\xb2

  • \n
  • 因此表达式最好是未计算的操作数 \xc2\xa78.2.3.1

    \n\n
    \n

    在某些情况下,会出现未计算的操作数([expr.prim.req]、[expr.typeid]、[expr.sizeof]、[expr.unary.noexcept]、[dcl.type.simple]、[temp])。\ n 未计算的操作数不被计算

    \n
  • \n
  • 未求值的操作数是通用表达式,但不能要求它们在编译时可求值,因为它们根本不求值。请注意,模板的参数不是计算表达式本身的一部分,而是命名模板类型的非限定 id 的一部分。这是我最初的困惑的一部分,并尝试寻找可能的实现。

  • \n
  • 非类型模板参数必须是常量表达式 \xc2\xa78.6,但此属性是通过求值定义的(我们已经确定这通常是不可能的)。\xc2\xa78.6.2

    \n\n
    \n

    表达式 e 是核心常量表达式,除非对e 的求值遵循抽象机的规则,[我自己强调]求值以下表达式之一:

    \n
  • \n
  • 用于noexpect未评估的上下文也有同样的问题:最好的鉴别器(推断的无异常)仅适用于可以评估为核心常量表达式的函数调用,因此此 stackoverflow 答案中提到的技巧不起作用。

  • \n
  • sizeof有同样的问题decltype。事情可能会改变concepts

  • \n
  • 遗憾的是,新引入的if constexpr不是一个表达式,而是一个带有表达式参数的语句。因此,它无助于加强constexpr表达式的可评估性。当语句被求值时,它的表达式也被求值,我们又回到了创建可求值的问题const T&。丢弃的语句对进程完全没有影响。

  • \n
\n\n

首先是简单的可能性

\n\n

由于最困难的部分是创建const T&,我们只需针对少数常见但容易确定的可能性进行创建,并将其余部分留给极其特殊情况的调用者进行专门化。

\n\n
namespace detail {\n    template <int> using Sink = std::true_type;\n\n    template<typename T,bool SFINAE=true> struct ConstexprDefault;\n    template<typename T>\n    struct ConstexprDefault<T, Sink<(T{}, 0)>::value> { inline static constexpr T instance = {}; };\n\n    template<typename T> constexpr auto constexpr_copiable(int) -> Sink<(T{ConstexprDefault<T>::instance}, 0)>;\n    template<typename T> constexpr auto constexpr_copiable(...) -> std::false_type;\n}\n\ntemplate<typename T>\nusing is_constexpr_copyable_t = decltype(detail::constexpr_copiable<T>(0));\n
Run Code Online (Sandbox Code Playgroud)\n\n

details::ConstexprDefault声明 constexpr 复制构造函数的任何类类型都必须可以进行特化,如上所示。请注意,该参数不适用于没有构造函数\xc2\xa76.7.2的其他复合类型。数组、联合、引用和枚举需要特别考虑。

\n\n

可以在 godbolt 上找到具有多种类型的“测试套件” 。非常感谢 reddit 用户 /u/dodheim,我从他那里复制了它。缺少的化合物类型的其他专业化留给读者作为练习。

\n\n

\xc2\xb2 或What does this leave us with?

\n\n

模板参数中的求值失败并不是致命的。SFINAE 可以覆盖多种可能的构造函数。本节的其余部分纯粹是理论性的,对编译器不利,否则可能会很愚蠢。

\n\n

使用类似于 的方法有可能枚举一个类型的许多构造函数magic_get。本质上,使用一种假装Ubiq可转换为所有其他类型的类型来伪造参数包decltype(T{ ubiq<I>()... }),其中I包含当前检查的初始值设定项计数,并且template<size_t i> Ubiq ubiq()仅构建正确数量的实例。T当然,在这种情况下,需要明确禁止强制转换。

\n\n

为什么只有很多?和以前一样,一些 constexpr 构造函数将存在,但它可能有访问限制。这会在我们的模板机中产生误报并导致无限搜索,并且有时编译器会死掉:/。或者构造函数可能被重载隐藏,该重载无法解析,因为Ubiq太笼统了。同样的效果,悲伤的编译器和愤怒的PETC人(道德对待编译器\xe2\x84\xa2的人,不是一个真正的组织)。实际上,访问限制可能是可以解决的,因为这些限制不适用于模板参数,这可能允许我们提取指向成员的指针和[...]。

\n\n

我就停在这里。据我所知,这很乏味,而且大多是不必要的。当然,对于大多数用例来说,覆盖可能的构造函数调用最多 5 个参数就足够了。任意性T是非常非常困难的,我们不妨等待 C++20,因为模板元编程将再次发生巨大变化。

\n