Implementing is_constexpr_copiable
Implementing is_constexpr_copiable
我试图实现一个类似于std::is_constructible
的值模板,但只有在 constexpr 环境中该类型可复制时才为 true(即其复制构造函数是 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, "");
现在我问自己这是否符合标准,因为编译器似乎不同意输出。 https://godbolt.org/g/Aaqoah
编辑(c++17 功能):
在实现略有不同的is_constexpr_constructible_from
时,使用 c++17 的新自动非类型模板类型,我再次发现编译器之间的差异,当使用SFINAE
在 constexpr 表达式中取消引用 nullptr 时。
#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() {}
https://godbolt.org/g/830SCU
编辑:(四月2018)
Clang接受一些未定义的行为,取消引用nullptr
,在计算未计算的decltype
操作数时。
最艰巨的挑战是,给出一个函数来评估任意 T 是否存在来自const T&
的constexpr
构造函数,在这里给出似乎几乎不可能在 C++17 中。幸运的是,我们可以走很长的路。其理由如下:
了解问题空间
以下限制对于确定是否可以在constexpr
内容中计算某些表达式非常重要:
-
要计算
T
的复制构造函数,需要一个类型const T&
的值。这样的值必须引用具有活动生存期的对象,即constexpr
上下文中,它必须引用在逻辑封闭表达式中创建的某个值。 -
为了创建此引用作为任意
T
的临时提升的结果,我们需要知道并调用一个构造函数,其参数可能涉及几乎任意的其他表达式,我们需要评估其constexpr
性。据我所知,这似乎需要解决确定一般表达式constexpr
性的一般问题。¹ -
¹ 实际上,如果任何带有参数的构造函数(包括复制构造函数)被定义为
对于constexpr
,则必须有一些有效的方法来构造T
,无论是作为聚合初始化还是通过构造函数。否则,程序的格式将不正确,这可以通过constexpr
说明符 §10.1.5.5 的要求来确定:既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,使得函数或构造函数的调用可能是核心常量表达式的计算子表达式,或者对于构造函数,是某个对象的常量初始值设定项 ([basic.start.static]),则程序格式不正确,无需诊断。
这可能会给我们一个小小的漏洞。
-
所以表达式最好是未计算的操作数 §8.2.3.1
在某些上下文中,会出现未计算的操作数([expr.prim.req]、[expr.typeid]、[expr.sizeof]、[expr.unary.noexcept]、[dcl.type.simple]、[temp])。 不计算未计算的操作数
-
未计算的操作数是通用表达式,但不能要求它们在编译时可计算,因为它们根本不被计算。请注意,模板的参数不是未计算表达式本身的一部分,而是命名模板类型的非限定 id 的一部分。这是我最初困惑的一部分,并试图找到可能的实现。
-
非类型模板参数必须是常量表达式 §8.6,但此属性是通过求值定义的(我们已经确定这通常是不可能的)。 §8.6.2
表达式 e 是一个核心常量表达式,除非 e 的计算遵循抽象机器的规则,会[由我自己突出显示]计算以下表达式之一:
-
对未计算的上下文使用
noexpect
具有相同的问题:最好的鉴别器,推断的 noexceptness,仅适用于可以作为核心常量表达式计算的函数调用,因此此 stackoverflow 答案中的技巧 mentionend 不起作用。 -
sizeof
有与decltype
相同的问题。事情可能会随着concepts
而改变。 -
可悲的是,新引入的
if constexpr
不是一个表达式,而是一个带有表达式参数的语句。因此,它无助于强制实施表达式的constexpr
可计算性。当语句被评估时,它的表达式也是如此,我们又回到了创建可评估const T&
的问题。丢弃的语句对流程完全没有影响。
简单的可能性第一
由于困难的部分是创建const T&
,我们只是为少数常见但易于确定的可能性这样做,其余的则由极其特殊的情况调用者进行专业化。
namespace detail {
template <int> using Sink = std::true_type;
template<typename T,bool SFINAE=true> struct ConstexprDefault;
template<typename T>
struct ConstexprDefault<T, Sink<(T{}, 0)>::value> { inline static constexpr T instance = {}; };
template<typename T> constexpr auto constexpr_copiable(int) -> Sink<(T{ConstexprDefault<T>::instance}, 0)>;
template<typename T> constexpr auto constexpr_copiable(...) -> std::false_type;
}
template<typename T>
using is_constexpr_copyable_t = decltype(detail::constexpr_copiable<T>(0));
对于声明 constexpr 复制构造函数的任何类类型,必须能够进行专用details::ConstexprDefault
,如上所示。请注意,该参数不适用于没有构造函数的其他复合类型 §6.7.2。数组、联合、引用和枚举需要特殊考虑。
可以在 godbolt 上找到具有多种类型的"测试套件"。非常感谢reddit用户/u/dodheim,我从他那里复制了它。缺失化合物类型的其他专业化留给读者作为练习。
² 或What does this leave us with?
模板参数中的评估失败不是致命的。SFINAE可以涵盖各种可能的构造函数。本节的其余部分纯粹是理论上的,对编译器来说并不好,否则可能会很愚蠢。
有可能使用类似于magic_get
的方法枚举一个类型的许多构造函数。本质上,使用一种类型Ubiq
假装可转换为所有其他类型的类型来伪造你的方式decltype(T{ ubiq<I>()... })
其中I
是一个参数包,其中包含当前检查的初始值设定项计数,template<size_t i> Ubiq ubiq()
只是构建正确数量的实例。当然,在这种情况下,需要明确禁止T
的演员阵容。
为什么只有很多?和以前一样,一些 constexpr 构造函数将存在,但它可能有访问限制。这会在我们的模板机器中给出误报并导致无限搜索,并且在某个时候编译器会死亡:/。或者构造函数可能被重载隐藏,由于Ubiq
太笼统而无法解决。同样的效果,悲伤的编译器和愤怒的PETC
(人们为编译器™的道德待遇,而不是一个真正的组织)。实际上,访问限制可以通过以下事实来解决:这些限制不适用于模板参数,这可能允许我们提取指向成员的指针和 [...]。
我就到此为止。据我所知,这很乏味,而且大多是不必要的。当然,对于大多数用例来说,涵盖可能的构造函数调用 5 个参数就足够了。任意T
非常非常困难,我们不妨等待 C++20,因为模板元编程将再次发生巨大变化。
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 多成员Constexpr结构初始化
- 条件constexpr函数
- constexpr 函数中的非文字(通过 std::is_constant_evaluated)
- Visual C++ constexpr Hints
- 如何确认我的constexpr表达式实际上已经在编译时执行
- 为什么constexpr的性能比正常表达式差
- 是否可以使用if constexpr删除控制流语句
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 为什么std::isnan 不是 constexpr?
- Constexpr替代了新的放置方式,可以让内存中的对象保持未初始化状态
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 更多constexpr容器是否需要mark_immutable_if_consexpr
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- constexpr上下文中std::initializer_list的验证
- constexpr构造函数需要常量成员函数时出现问题
- vs 2015 constexpr变量不恒定,但与2019相比还好吗
- C++constexpr实现差异
- 添加静态constexpr成员是否会更改结构/类的内存映射
- C++中的Constexpr迭代程序