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)
编辑:( 2018年4月)
Clang nullptr
在评估未评估的操作数时接受一些未定义的行为,取消引用decltype
.
最艰难的挑战是给出一个函数来评估任意 T 的constexpr
构造函数是否存在,这里给出的在 C++17 中似乎几乎不可能。const T&
幸运的是,我们可以在没有这些的情况下走很长的路。其理由如下:
以下限制对于确定是否可以在constexpr
内容中评估某些表达式非常重要:
要计算 的复制构造函数,需要T
type 的值。const T&
这样的值必须引用具有活动生命周期的对象,即在constexpr
上下文中它必须引用在逻辑封闭表达式中创建的某个值。
为了创建此引用作为任意临时提升的结果,T
我们需要知道并调用一个构造函数,其参数实际上可能涉及任意其他表达式,constexpr
我们需要评估其 -ness。constexpr
据我所知,这看起来需要解决确定一般表达式的性质的一般问题。\xc2\xb9
\xc2\xb9 实际上,如果任何带有参数的构造函数(包括复制构造函数)被定义为constexpr
,则必须有某种有效的方法来构造T
,无论是作为聚合初始化还是通过构造函数。否则,程序将是错误的,这可以通过constexpr
说明符 \xc2\xa710.1.5.5的要求来确定:
\n\n\n对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,则函数或构造函数的调用可以是核心常量表达式的计算子表达式,或者对于构造函数,可以是常量初始值设定项某些对象([basic.start.static]),程序格式错误,无需诊断。
\n
这可能会给我们带来一个微小的漏洞。\xc2\xb2
因此表达式最好是未计算的操作数 \xc2\xa78.2.3.1
\n\n\n在某些情况下,会出现未计算的操作数([expr.prim.req]、[expr.typeid]、[expr.sizeof]、[expr.unary.noexcept]、[dcl.type.simple]、[temp])。\ n 未计算的操作数不被计算
\n
未求值的操作数是通用表达式,但不能要求它们在编译时可求值,因为它们根本不求值。请注意,模板的参数不是未计算表达式本身的一部分,而是命名模板类型的非限定 id 的一部分。这是我最初的困惑的一部分,并尝试寻找可能的实现。
非类型模板参数必须是常量表达式 \xc2\xa78.6,但此属性是通过求值定义的(我们已经确定这通常是不可能的)。\xc2\xa78.6.2
\n\n\n表达式 e 是核心常量表达式,除非对e 的求值遵循抽象机的规则,将[我自己强调]求值以下表达式之一:
\n
用于noexpect
未评估的上下文也有同样的问题:最好的鉴别器(推断的无异常)仅适用于可以评估为核心常量表达式的函数调用,因此此 stackoverflow 答案中提到的技巧不起作用。
sizeof
有同样的问题decltype
。事情可能会改变concepts
。
遗憾的是,新引入的if constexpr
不是一个表达式,而是一个带有表达式参数的语句。因此,它无助于加强constexpr
表达式的可评估性。当语句被求值时,它的表达式也被求值,我们又回到了创建可求值的问题const T&
。丢弃的语句对进程完全没有影响。
由于最困难的部分是创建const T&
,我们只需针对少数常见但容易确定的可能性进行创建,并将其余部分留给极其特殊情况的调用者进行专门化。
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\ndetails::ConstexprDefault
声明 constexpr 复制构造函数的任何类类型都必须可以进行特化,如上所示。请注意,该参数不适用于没有构造函数\xc2\xa76.7.2的其他复合类型。数组、联合、引用和枚举需要特别考虑。
可以在 godbolt 上找到具有多种类型的“测试套件” 。非常感谢 reddit 用户 /u/dodheim,我从他那里复制了它。缺少的化合物类型的其他专业化留给读者作为练习。
\n\nWhat does this leave us with?
模板参数中的求值失败并不是致命的。SFINAE 可以覆盖多种可能的构造函数。本节的其余部分纯粹是理论性的,对编译器不利,否则可能会很愚蠢。
\n\n使用类似于 的方法有可能枚举一个类型的许多构造函数magic_get
。本质上,使用一种假装Ubiq
可转换为所有其他类型的类型来伪造参数包decltype(T{ ubiq<I>()... })
,其中I
包含当前检查的初始值设定项计数,并且template<size_t i> Ubiq ubiq()
仅构建正确数量的实例。T
当然,在这种情况下,需要明确禁止强制转换。
为什么只有很多?和以前一样,一些 constexpr 构造函数将存在,但它可能有访问限制。这会在我们的模板机中产生误报并导致无限搜索,并且有时编译器会死掉:/。或者构造函数可能被重载隐藏,该重载无法解析,因为Ubiq
太笼统了。同样的效果,悲伤的编译器和愤怒的PETC
人(道德对待编译器\xe2\x84\xa2的人,不是一个真正的组织)。实际上,访问限制可能是可以解决的,因为这些限制不适用于模板参数,这可能允许我们提取指向成员的指针和[...]。
我就停在这里。据我所知,这很乏味,而且大多是不必要的。当然,对于大多数用例来说,覆盖可能的构造函数调用最多 5 个参数就足够了。任意性T
是非常非常困难的,我们不妨等待 C++20,因为模板元编程将再次发生巨大变化。
归档时间: |
|
查看次数: |
527 次 |
最近记录: |