如何为结构化绑定编写概念?

时间:2018-03-17 19:07:41

标签: c++ gcc c++-concepts

编译以下(简化)代码时:

'/\\.js$/'

使用gcc特定选项#include <tuple> #include <stdlib.h> template<size_t N> struct tying; template<> struct tying<1> {static auto function(auto& o) -> decltype(auto) {auto& [p1] = o;return std::tie(p1);}}; template<> struct tying<2> {static auto function(auto& o) -> decltype(auto) {auto& [p1,p2] = o;return std::tie(p1,p2);}}; template<typename T, size_t N> concept bool n_components = requires(T& object) { { tying<N>::function(object) }; }; typedef struct { int a; float b; } test_t; int main(int argc, char* argv[]) { constexpr size_t n = 1; constexpr bool t = n_components<test_t, n>; printf("n_components<test_t, %d>: %s\n", n, t ? "yes" : "nope"); return 0; } gcc(版本7.3.0和godbolt上的x86-64 gcc-trunk)也失败并显示错误:

  

错误:只为结构化装订提供了1个名称。注意:while&#39; test_t&#39;分解为2个元素

我对此感到非常惊讶,因为错误&#34;提升&#34;在-std=c++1z -fconcepts内,根据我的理解,这会导致requires-expression约束被评估为false,因为不满足要求。

注意:将n_components替换为其他值&#34;修复&#34;错误,所以我认为constexpr size_t n = 1;约束不应该受到责备:

  • 替换为n_components n = 2约束评估为&#34; true&#34;正如所料。
  • 替换为n_components n = 3约束评估为&#34; false&#34;正如预期的那样 - 由于引用了非专业化的n_components结构。

似乎没有其他支持c ++概念和结构化绑定的编译器可供比较。

PS。我正在玩着#34;穷人的反思&#34; a.k.a. tying<3>并希望用更强大的东西取代不可靠的magic_get特质......

2 个答案:

答案 0 :(得分:0)

即时情况

  

我对此感到非常惊讶,因为错误“提升”在requires-expression

准确地说,错误发生在你写的function的正文中。我们可以将您的代码归结为:

void function(auto& arg);

void function_with_body(auto& arg)
{
    arg.inexistent_member;
}

template<typename Arg>
concept bool test = requires(Arg arg) { function(arg); };

// never fires
static_assert( test<int> );

template<typename Arg>
concept bool test_with_body = requires(Arg arg) { function_with_body(arg); };

// never fires
static_assert( test_with_body<int> );

on Coliru

requires表达式而言,函数调用是有效的C ++表达式:它们的返回和参数类型没有什么棘手的,并且提供的参数可以很好地传递。不对函数体执行检查,并且有充分的理由:函数或函数模板可能已经声明但尚未定义(即function的情况)。相反,通常由函数模板编写者来确保函数体对所有(合理的)特化都没有错误。

您的代码使用了返回类型推导,但这并没有太大的区别:您可以声明一个具有占位符类型的函数(例如decltype(auto))而不定义它。 (虽然对这样一个函数的调用实际上是一个无效的表达式,因为该类型不能被推导出来并且对于requires表达式是可见的,但这不是你正在做的事情。)前概念说法,返回类型演绎不是SFINAE友好的。

结构化绑定

关于围绕结构化绑定编写约束的更广泛问题,你运气不好。据我所知,这些都是障碍:

  • 没有用于查询类型的分解大小的接口,即使实现公然可以访问信息以便对结构化绑定声明进行类型检查。 (std::tuple_size仅用于类似元组的类型。)。这可以通过盲目地尝试增加顺序的大小来解决。
  • 您只能在类型和表达式周围编写约束,但结构化绑定只能出现在常规变量声明中。 (如果函数参数可以是结构化绑定,我们可以解决这个问题。)不允许使用lambda表达式,因为它们不能出现在未评估的操作数中,例如在requires表达式的主体中。

您最接近的是模拟实际结构化绑定期间语言正在执行的操作。这可以让你支持类似元组的类型(包括数组),但不支持'哑'聚合。

range-for是一个语句级约束的另一种情况,你无法将其置于表达式约束中,并且模拟语言只能让你到目前为止。)

答案 1 :(得分:0)

从5.0开始,这是clang的部分解决方案。
它是根据最近的Reddit帖子摘录的 Find the number of structured bindings for any struct

该方法是非标准的,使用gnu扩展语句表达式有效地对结构化绑定声明进行SFINAE。这也是不可移植的,因为gcc无法以lambda尾随返​​回类型解析语句表达式中的结构化绑定。

template <typename... Ts> struct overloads : Ts... { using Ts::operator()...; };
template <typename... Ts> overloads(Ts...) -> overloads<Ts...>;

template <typename T>
auto num_bindings_impl() noexcept {
    return overloads{
        [](auto&& u, int) -> decltype(({auto&& [x0] = u; char{};}))(*)[1] {return {};},
        [](auto&& u, int) -> decltype(({auto&& [x0,x1] = u; char{};}))(*)[2] {return {};},
        [](auto&& u, int) -> decltype(({auto&& [x0,x1,x2] = u; char{};}))(*)[3] {return {};},
        [](auto&& u, int) -> decltype(({auto&& [x0,x1,x2,x3] = u; char{};}))(*)[4] {return {};},
        [](auto&& u, unsigned) -> void {}
    }(declval<T>(), int{});
};

此实现只能在未评估的上下文中使用并扩展
尽可能多的绑定,直至编译器实现限制。

然后,您可以定义可以达到该限制的特征:

template <typename T>
inline constexpr bool has_bindings = [] {
    if constexpr ( ! is_empty_v<T> )
        return ! is_void_v< decltype(num_bindings_impl<T>()) >;
    else return false;
}();

template <typename T>
inline constexpr unsigned num_members = [] {
    if constexpr ( ! is_empty_v<T> )
        return sizeof *num_bindings_impl<T>();
    else return 0;
}();

https://godbolt.org/z/EVnbqj