返回可变参数聚合体(结构体)和C++17可变模板“构造推断指南”的语法

7

使用像下面这样的模板结构体many,可以返回一组固定的可能是不可移动对象,并使用c++17结构化绑定(auto [a, b, c] = f();声明变量abc并从f返回的结构体或元组中分配它们的值)。

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};

// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;

auto f(){ return many{string(),5.7, unmovable()}; }; 

int main(){
   auto [x,y,z] = f();
}

如这两个问题和答案所解释的那样(std::tuple和std::pair是否支持聚合初始化?,尤其是ecatmur所接受的答案,还有C++17中具有不可移动类型和保证RVO的多返回值(结构化绑定)),std::tuple不支持聚合初始化。这意味着它不能用于保存和返回不可移动类型。但是像many这样的简单结构体可以做到这一点,这就引出了以下问题:
能否创建一个与任意数量参数一起使用的可变参数版本的many
更新:在many的模板化版本中,以下指南语法是否允许?
template<typename Args...>    
many(Args...) -> many<Args...>;

请注意,有一个类似于“auto std::tie(a, b, c) = ...”的提案。 - lorro
@lorro 我认为这是一个不好的主意:它让核心语言特性表现为库特性。更不用说编译器将不得不解决潜在的using、命名空间别名和其他问题了。 - Revolver_Ocelot
2
@lorro:该功能已经被批准,并包含在最新的C++17草案(N4606)中,但语法是 auto [a, b, c] = ...; - ildjarn
是的,@ildjan,这是这个问题的前提条件,并且在问题中被使用。 - Johan Lundberg
@lorro,也许你错过了ildjarn的话,他说这个已经被投票通过了,但不是你写的语法,而是我在问题中已经使用的语法,还有更多相关的问题。或者你真的是想要添加使用那种语法的可能性吗? - Johan Lundberg
显示剩余2条评论
2个回答

6
在C++17中,聚合初始化将能够初始化公共基类。因此,您可以使用继承+包扩展来构建这样的类。为了使其与结构化绑定配合使用,您必须公开元组接口:专门化std::tuple_sizestd::tuple_element,并为您的类提供get函数。
//Headers used by "many" class implementation
#include <utility>
#include <tuple>

namespace rw {
    namespace detail {

    template <size_t index, typename T>
    struct many_holder
    { T value; };

    template <typename idx_seq, typename... Types>
    struct many_impl;

    template <size_t... Indices, typename... Types>
    struct many_impl<std::index_sequence<Indices...>, Types...>: many_holder<Indices, Types>...
    {};

    }

template <typename... Types>
struct many: detail::many_impl<typename std::make_index_sequence<sizeof...(Types)>, Types...>
{};

template<size_t N, typename... Types> 
auto get(const rw::many<Types...>& data) -> const std::tuple_element_t<N, rw::many<Types...>>&
{
    const rw::detail::many_holder<N, std::tuple_element_t<N, rw::many<Types...>>>& holder = data;
    return holder.value;
}

}

namespace std {
    template <typename... Types>
    struct tuple_size<rw::many<Types...>> : std::integral_constant<std::size_t, sizeof...(Types)> 
    {};

    template< std::size_t N, class... Types >
    struct tuple_element<N, rw::many<Types...> >
    { using type = typename tuple_element<N, std::tuple<Types...>>::type; };
}

//Headers used for testing
#include <iostream>
#include <string>

int main()
{
    rw::many<int, std::string, int> x = {42, "Hello", 11};
    std::cout << std::tuple_size<decltype(x)>() << '\n' << rw::get<1>(x);
}

Demo (目前只能在clang 3.9中运行):http://melpon.org/wandbox/permlink/9NBqkcbOuURFvypt

注:

  • 演示中有一个已注释掉的nth_type实现,您可以使用它直接实现tuple_element而不将其推迟到tuple_element<tuple>实现中。
  • get<many>应该是many的成员函数或放置在相关联的命名空间中才能使结构化绑定正常工作。您不应重载std::get(无论如何都会导致未定义行为)。
  • 我留下了非const引用和右值引用的get实现作为读者的练习。
  • 没有使用结构化绑定和指南的示例,因为clang不支持它们(事实上,我不知道任何支持它们的编译器)。理论上
    template<typename... Types> many(Types...) -> many<Types...>;应该可行。

不错,但问题也在于如何使用推断的模板参数使其与我的三个成员示例一样工作。类似于 template<typename Args...> many(Args...) -> many<Args...>; 这样的东西? - Johan Lundberg
@JohanLundberg 如果你的原始代码有效,我想你只需要将你的“指南”改为使用可变模板参数。但是我找不到任何在线编译器能够接受你的原始代码,所以我无法测试。 - Revolver_Ocelot
1
@Revolver_Ocelot: 要使这个工作起来,你必须给many提供tuple_element和其他接口,以便结构化绑定能够工作。因为结构化绑定无法获取基类。此外,您不能两次使用相同的基类,因此您不能在Types中具有相同的类型。 - Nicol Bolas
1
@NicolBolas 是的。我错认为结构化绑定遵循“后向聚合初始化”的规则。对于重复类型,我可能会向持有者结构添加虚拟的size_t参数,并使用索引序列枚举所有基类,强制每种类型都是唯一的(据我所知,元组实现通常正是这样做的)。也许会在一个小时左右更新我的答案。 - Revolver_Ocelot

2

就在几天前,std-proposals上有关于这个问题的讨论

我们还没有最终措辞,或者说我所知道的编译器还没有支持推导指南,但是据Richard Smith说,以下推导指南应该可以工作(简述):

template<class A, class B>
struct Agg
{
    A a;
    B b;
};

template<class A, class B>
Agg(A a, B b) -> Agg<A, B>;

Agg agg{1, 2.0}; // deduced to Agg<int, double>

是的,对于一个聚合体来说,可变参数模板推导指南也适用,并且可以使用聚合体初始化语法。如果没有推导指南,它将不起作用,因为编译器需要一个构造函数。


2
问题在于您无法拥有一个成员是可变参数的类型。也就是说,您不能像这样做 Types varname...; - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接