使用单参数模板化构造函数,从引用元组构造值元组

Constructing tuple of value from tuple of reference, with one-argument templated constructor

本文关键字:元组 构造函数 引用 单参数 参数      更新时间:2023-10-16

我有一个常量引用元组std::tuple<const Matrix&, ...>我从中构造一个值元组std::tuple<Matrix, ...>。对于任何大于 1 的元组大小,这都可以正常工作:(在线示例:https://godbolt.org/g/24E8tU)

#include <tuple>
struct Matrix {
Matrix() = default;
Matrix(Matrix const&) = default;
template <typename T>
explicit Matrix(T const&) {
// in reality, this comes from Eigen, and there is real work
// being done here. this is just to demonstrate that the code
// below fails
static_assert(std::is_same<T, int>::value, "!");
}
};
void works() {
Matrix m1, m2;
std::tuple<const Matrix &, const Matrix &> tuple_of_ref{m1, m2};
std::tuple<Matrix, Matrix> t{tuple_of_ref};
}

但是,对于大小为 1 的元组,此代码无法编译:

void fails() {
Matrix m;
std::tuple<const Matrix &> tuple_of_ref{m};
// Tries and fails to instantiate Matrix(std::tuple<const Matrix &>)
std::tuple<Matrix> t{tuple_of_ref};
}

请注意,Matrix类有一个接受std::tuple的模板化构造函数。

template<typename T>
explicit Matrix(const T& x)

我不想使用此构造函数,并且由于它是第三方代码,因此无法更改它。

我认为我的works()示例正确调用了 cpp首选项上列为 #4 的构造函数:

template< class... UTypes >
tuple( const tuple<UTypes...>& other );

4)转换复制构造函数。对于sizeof...(UTypes)中的所有 i,用std::get<i>(other)初始化元组的第 i 个元素。

fails()示例尝试使用此构造函数,大概是我不想要的 #3:

template< class... UTypes >
explicit tuple( UTypes&&... args );

3) 转换构造函数。使用std::forward<Utypes>(args)中的相应值初始化元组的每个元素。

如何确保tuple的构造函数 #4 用于这两种情况?我真正的用例是在可变参数模板中,所以我事先不知道元组的大小。

是的,所以...这是问题所在:

template<typename T>
explicit Matrix(const T& x)

这是一个非常不友好的构造函数 - 因为它在撒谎。Matrix实际上不能从任何东西中构造出来,只是一些特定的东西 - 但是没有办法从外部检测这些东西是什么。

在考虑如何从tuple<Matrix const&>构建tuple<Matrix>时,我们有很多选择,但实际上只有两个是可行的:

// #2, with Types... = {Matrix}
tuple(Matrix const&);
// #3, with UTypes = {tuple<Matrix const&>&}
tuple(tuple<Matrix const&>&);

两者都最终试图从tuple<Matrix const&>构建Matrix,这不起作用,你被卡住了。


现在,您可能会认为 #4 是您在这里的救赎 - 生成一个构造函数,即:

tuple(tuple<Matrix const&> const& );

并从tuple论证的基本Matrix构建其基础Matrix。也就是说,使用转换复制构造函数。似乎问题在于这个构造函数有效,但无论出于何种原因(即它需要不太符合 cv 条件的引用),并且解决方案是尝试摆弄参数,以便首选 #4(即在参数上使用as_const())。

但是该构造函数并不是更受欢迎的...它实际上在这里不可行,因为对该构造函数的限制是(来自 LWG 2549):

要么sizeof...(Types) != 1,要么(当Types...扩展到TUTypes...扩展到U时)is_­convertible_­v<const tuple<U>&, T>is_constructible_­v<T, const tuple<U>&>is_­same_­v<T, U>都是false

但我们确实有sizeof..(Types) == 1,这些事情并不全是假的(特别是,第二个是真的 - 这是你所有问题的根源),所以#4根本不是一个候选人,也没有一个巧妙的技巧来让它成为一个。


那么,如何解决呢?到目前为止,最好的办法是修复Matrix。我意识到这可能不太可能,但必须说。

您可以将Matrix包装在实际上向其构造函数添加约束以避免此问题的内容中。由于您已经将其复制到tuple中,这使您有机会执行以下简单操作:

template <typename T>
struct only_copyable {
only_copyable(only_copyable const& ) = default;
only_copyable(T const& t) : t(t) { }
template <typename U> only_copyable(U const& ) = delete;
T t;
};

但你可能想要比这更现实的东西。此外,您的类型可以只是从Matrix继承,并且只是为了理智而摆弄其构造函数。

或者,在专门处理大小为 1 的tuple时,您可以避免使用元组构造函数,而只使用默认构造/赋值。或者明确地调用get<0>或类似的东西。

或者,您可以完全避免尺寸为 1 的tuple。这是一件奇怪的具体事情,但也许这就足够了(或者你可以以这样一种方式包装tuple,即my_tuple<A, B, C...>tuple<A,B,C...>my_tuple<A>真的很tuple<A, monostate>)。

但真的...修复Matrix构造函数似乎非常值得。

不完全是你问的,但是...如何传递中间模板函数tplHelper()在更通用的情况下,只需返回收到的值

template <typename T>
T tplHelper (T const & tpl)
{ return tpl; }

但是在具有单一类型的std::tuple的情况下返回包含的值?

template <typename T>
T tplHelper (std::tuple<T> const & tpl)
{ return std::get<0>(tpl); }

如果创建t传递tplHelper()

std::tuple<Matrix, Matrix> t{ tplHelper(tuple_of_ref) };
// ...
std::tuple<Matrix> t{ tplHelper(tuple_of_ref) };

当您有两个或微尘类型时,您继续调用std::tuple的复制构造函数,但是当您使用具有单个矩阵的元组调用它时,您将避免将模板Matrix构造函数并调用 COPYMatrix构造函数。

以下是完整的工作示例

#include <tuple>
struct Matrix
{
Matrix ()
{ }
template <typename T>
explicit Matrix (const T &)
{
// This constructor fails to compile when T is std::tuple<...>
// and I don't want to use it at all
static_assert(sizeof(T) == 0, "!");
}
};
template <typename T>
T tplHelper (T const & tpl)
{ return tpl; }
template <typename T>
T tplHelper (std::tuple<T> const & tpl)
{ return std::get<0>(tpl); }
void m2 ()
{
Matrix m1, m2;
std::tuple<const Matrix &, const Matrix &> tuple_of_ref{m1, m2};
std::tuple<Matrix, Matrix> t{ tplHelper(tuple_of_ref) };
}
void m1 ()
{
Matrix m;
std::tuple<const Matrix &> tuple_of_ref{m};
// now compile!
std::tuple<Matrix> t{ tplHelper(tuple_of_ref) };
}

int main ()
{
m2();
m1();
}

如果不有效地重新实现您想要调用的tuple构造函数,我想不出解决问题的好方法:

struct TupleFromTuple{};
template<class... T, class... U>
struct TupleFromTuple<std::tuple<T...>, std::tuple<U...>>
{
static_assert(sizeof...(T) == sizeof...(U), "Tuples should be the same size");
using to_t = std::tuple<T...>;
using from_t = std::tuple<U...>;
static to_t Apply(from_t& tup)
{
return ApplyImpl(tup, std::index_sequence_for<T...>{});
}
private:
template<size_t... I>
static to_t ApplyImpl(from_t& tup, std::index_sequence<I...>){
return {std::get<I>(tup)...};
}
};

演示

使用一些轻量级 C++14,但没有在 C++11 中无法实现的内容

实际上,我们使用索引序列来调用自己std::get。 给定一些垃圾实现这样的Matrix

struct Matrix
{
Matrix() = default;
template<class T>
explicit Matrix(const T& foo){foo.fail();}
};

您的fails测试现在通过:

void fails() {
Matrix m;
std::tuple<const Matrix &> tuple_of_ref{m};
auto t = TupleFromTuple<std::tuple<Matrix>, decltype(tuple_of_ref)>::Apply(tuple_of_ref);
}

我会通过在"源"元组上调用std::get来规避这个问题:

Thing thing;
std::tuple<Thing const &> get_source{thing};
std::tuple<Thing> get_target{std::get<0>(get_source)};

这样可以避免调用显式构造函数,而是调用复制构造函数。

为了将其推广到任何长度的元组中,您可以使用std::integer_sequence以及基于它构建的内容,并将所有这些内容包装在一个函数中:

template<typename T>
using no_ref_cv = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template<typename... T, std::size_t... Idx>
auto MakeTupleWithCopies_impl(std::tuple<T...> const & source, std::index_sequence<Idx...>) {
return std::tuple<no_ref_cv<T>...>{std::get<Idx>(source)...};
}
template<typename... T>
auto MakeTupleWithCopies(std::tuple<T...> const & source) {
return MakeTupleWithCopies_impl(source, std::index_sequence_for<T...>{});
}

坚持使用 C++11?

std::integer_sequence和它的朋友也可以用 C++11 编写(不是完全替换,例如错过成员函数,并且可能会中断signed整数类型):

template<typename T, T...>
struct integer_sequence {};
template<std::size_t... Ints>
using index_sequence = integer_sequence<std::size_t, Ints...>;

template<typename T, T... t>
integer_sequence<T, t..., sizeof...(t)> inc(integer_sequence<T, t...>) {
return {};
}
template<typename T, T N, std::size_t Count>
struct make_integer_sequence_help {
using type = decltype(inc(typename make_integer_sequence_help<T,N,Count - 1>::type{}));
};
template<typename T, T N>
struct make_integer_sequence_help<T, N, 0> {
using type = integer_sequence<T>;
};
template<class T, T N>
using make_integer_sequence = typename make_integer_sequence_help<T,N, N>::type;

template<std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
template<class... T>
using index_sequence_for = make_index_sequence<sizeof...(T)>;

然后你只需要将两个函数的auto返回类型规范更改为std::tuple<no_ref_cv<T>...>