我试图实现一个类似于 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, "");
现在我问自己这是否符合标准,因为编译器似乎不同意输出 . https://godbolt.org/g/Aaqoah
编辑(c 17功能):
在使用c 17的新自动非类型模板类型实现稍微不同的 is_constexpr_constructible_from
时,我再次发现编译器之间存在差异,当使用 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() {}
编辑:( 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的行为是公认的标准化行为,我会将此版本提升到一个答案,因为它只需要完全要求的内容 .
在评估 decltype
的未评估操作数时,Clang接受一些未定义的行为,解除引用 nullptr
.
1 回答
最艰难的挑战,给出一个函数来评估
const T&
中的constexpr
构造函数是否存在于任意T,这里给出的C 17几乎不可能 . 幸运的是,我们可以在没有的情况下走很长的路 . 其原因如下:了解问题空间
以下限制对于确定是否可以在
constexpr
内容中评估某个表达式非常重要:要评估
T
的复制构造函数,需要类型为const T&
的值 . 这样的值必须引用具有活动生命周期的对象,即在constexpr
上下文中,它必须引用在逻辑上封闭的表达式中创建的某个值 .为了创建这个引用作为临时推广任意
T
的结果,因为我们需要知道并调用构造函数,其参数可能涉及我们需要评估的几乎任意其他表达式constexpr
. 就我所知,这似乎需要解决确定一般表达式的一般问题 . ¹¹实际上,如果任何带参数的构造函数(包括复制构造函数)被定义为
constexpr
,则必须有一些有效的构造T
的方法,无论是作为聚合初始化还是通过构造函数 . 否则,该程序将是不正确的,可以通过constexpr specifier §10.1.5.5的要求来确定:这可能会给我们带来一个小小的漏洞
未评估的操作数是通用表达式,但在编译时不能要求它们可评估,因为它们根本不被评估 . 请注意,模板的参数不是未评估表达式本身的一部分,而是命名模板类型的非限定id的一部分 . 这是我最初的困惑的一部分,并试图找到一个可能的实现 .
非类型模板参数必须是constant expressions §8.6,但此属性是通过评估定义的(我们已经确定通常不可能) . §8.6.2
对于未评估的上下文使用
noexpect
具有相同的问题:最佳鉴别器,推断noexceptness,仅适用于可以作为核心常量表达式求值的函数调用,因此this stackoverflow answer中提到的技巧不起作用 .sizeof
与decltype
有同样的问题 . 事情可能随着concepts
而改变 .遗憾的是,新引入的
if constexpr
不是表达式,而是带有表达式参数的语句 . 因此,它无助于强制执行表达式的可评估性 . 在评估语句时,它的表达式也是如此,我们又回到了创建可评估的const T&
的问题 . 废弃的陈述根本不会对流程产生影响 .首先是简单的可能性
由于困难部分正在创造
const T&
,我们只是为少数常见但很容易确定的可能性做到这一点,并让其他人通过极其特殊的案例来电专业化 .如上所示,对于声明constexpr复制构造函数的任何类类型,必须可以使用
details::ConstexprDefault
. 请注意,该参数不适用于没有构造函数§6.7.2的其他复合类型 . 数组,联合,引用和枚举需要特殊考虑 .可以找到'test suite',其中包含多种类型on godbolt . 非常感谢reddit用户/ u / dodheim from whom I have copied it . 缺少的化合物类型的其他专业化留给读者练习 .
²或者这给我们带来了什么?
模板参数的评估失败并不是致命的 . SFINAE可以涵盖各种可能的构造函数 . 本节的其余部分纯粹是理论上的,对编译器不好,否则可能显然是愚蠢的 .
可能使用类似于magic_get的方法枚举类型的许多构造函数 . 基本上,使用类型
Ubiq
假装可以转换为所有其他类型以伪装你的方式decltype(T{ ubiq<I>()... })
其中I
是一个参数包,当前检查的初始化项目计数和template<size_t i> Ubiq ubiq()
只是构建正确数量的实例 . 当然,在这种情况下,需要明确禁止转换为T
.为什么只有很多?和以前一样,一些constexpr构造函数将存在,但它可能具有访问限制 . 这会在我们的模板机中产生误报并导致无限搜索,并且在某些时候编译器会死:/ . 或者构造函数可能被重载隐藏,无法解析,因为
Ubiq
过于笼统 . 同样的效果,悲伤的编译器和愤怒PETC
(人们对编译器的道德处理,而不是真正的组织) . 实际上,访问限制可以通过以下事实来解决:这些限制不适用于模板参数,这可能允许我们提取指向成员的指针和[...] .我很乏味,而且大部分都是不必要的 . 当然,对于大多数用例来说,覆盖可能的构造函数调用5个参数就足够了 . 任意
T
是非常非常努力的我们不妨等待C 20作为模板元编程再次大规模地改变 .