首页 文章

实现is_constexpr_copiable

提问于
浏览
20

我试图实现一个类似于 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() {}

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的行为是公认的标准化行为,我会将此版本提升到一个答案,因为它只需要完全要求的内容 .
在评估 decltype 的未评估操作数时,Clang接受一些未定义的行为,解除引用 nullptr .

1 回答

  • 2

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

    了解问题空间

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

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

    • 为了创建这个引用作为临时推广任意 T 的结果,因为我们需要知道并调用构造函数,其参数可能涉及我们需要评估的几乎任意其他表达式 constexpr . 就我所知,这似乎需要解决确定一般表达式的一般问题 . ¹

    • ¹实际上,如果任何带参数的构造函数(包括复制构造函数)被定义为 constexpr ,则必须有一些有效的构造 T 的方法,无论是作为聚合初始化还是通过构造函数 . 否则,该程序将是不正确的,可以通过constexpr specifier §10.1.5.5的要求来确定:

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

    这可能会给我们带来一个小小的漏洞

    在某些情况下,会出现未评估的操作数([expr.prim.req],[expr.typeid],[expr.sizeof],[expr.unary.noexcept],[dcl.type.simple],[temp]) . 未评估未评估的操作数

    • 未评估的操作数是通用表达式,但在编译时不能要求它们可评估,因为它们根本不被评估 . 请注意,模板的参数不是未评估表达式本身的一部分,而是命名模板类型的非限定id的一部分 . 这是我最初的困惑的一部分,并试图找到一个可能的实现 .

    • 非类型模板参数必须是constant expressions §8.6,但此属性是通过评估定义的(我们已经确定通常不可能) . §8.6.2

    安表达式e是一个核心常量表达式,除非按照抽象机器的规则评估e将[自己突出显示]评估以下表达式之一:

    • 对于未评估的上下文使用 noexpect 具有相同的问题:最佳鉴别器,推断noexceptness,仅适用于可以作为核心常量表达式求值的函数调用,因此this stackoverflow answer中提到的技巧不起作用 .

    • sizeofdecltype 有同样的问题 . 事情可能随着 concepts 而改变 .

    • 遗憾的是,新引入的 if 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的其他复合类型 . 数组,联合,引用和枚举需要特殊考虑 .

    可以找到'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作为模板元编程再次大规模地改变 .

相关问题