使用单参数模板化构造函数从引用元组构造值元组
Constructing tuple of value from tuple of reference, with one-argument templated constructor
我有一个 const 引用 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
class 有一个接受 std::tuple
.
的模板化构造函数
template<typename T>
explicit Matrix(const T& x)
我不想使用这个构造函数,而且我无法更改它,因为它是第三方代码。
我认为我的 works()
示例正确调用了 cppreference 上列为 #4 的构造函数:
template< class... UTypes >
tuple( const tuple<UTypes...>& other );
4) Converting copy-constructor. For all i in sizeof...(UTypes)
, initializes ith element of the tuple with std::get<i>(other)
.
fails()
示例尝试使用 this constructor,大概是我不想要的 #3:
template< class... UTypes >
explicit tuple( UTypes&&... args );
3) Converting constructor. Initializes each element of the tuple with the corresponding value in std::forward<Utypes>(args)
.
如何确保 tuple
的构造函数 #4 用于这两种情况?我的真实用例是在可变参数模板中,所以我事先不知道元组的大小。
我会通过在 "source" 元组上调用 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
return类型规范改为std::tuple<no_ref_cv<T>...>
即可。
是的,所以...这就是问题所在:
template<typename T>
explicit Matrix(const T& x)
那是一个非常不友好的构造函数 - 因为它在撒谎。 Matrix
实际上不能从 任何东西 构造,只是一些特定的东西 - 但没有办法从外部检测这些东西是什么。
考虑如何从tuple<Matrix const&>
构造tuple<Matrix>
时,我们有lots of choices,但实际上只有两个可行:
// #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 限定引用),#3 都是首选,解决方案是尝试使用参数 fiddle 以便 # 4 是首选(即通过在参数上使用 as_const()
)。
但是那个构造函数并不是更受欢迎...它实际上 在这里不可行 因为对 that constructor is (from LWG 2549 的限制):
either sizeof...(Types) != 1
, or (when Types...
expands to T
and UTypes...
expands to U
) is_convertible_v<const tuple<U>&, T>
, is_constructible_v<T, const tuple<U>&>
, and is_same_v<T, U>
are all 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
和 fiddle 及其构造函数。
或者,在专门处理大小为 1 的 tuple
时,您可以避免使用元组构造函数,而只使用默认值 construct/assign。或者显式调用 get<0>
或类似的东西。
或者,您可以完全避免 tuple
大小为 1 的文件。这是一件奇怪的具体事情,但也许这就足够了(或者你可以将 tuple
包装成 my_tuple<A, B, C...>
是 tuple<A,B,C...>
但 my_tuple<A>
实际上是 tuple<A, monostate>
).
但真的...修复 Matrix
构造函数似乎非常值得。
如果不有效地重新实现您想要调用的 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)...};
}
};
Demo
使用了一些轻量级的 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);
}
不完全是你问的,但是......通过一个中间模板函数怎么样tplHelper()
,在更通用的情况下,只是return收到的值
template <typename T>
T tplHelper (T const & tpl)
{ return tpl; }
但如果 std::tuple
具有单一类型 return 包含的值 ?
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) };
当你有两个or mote类型时,你继续调用std::tuple
的拷贝构造函数,但是当你用单矩阵的元组调用它时,你避免模板Matrix
构造函数并调用副本 Matrix
构造函数。
以下是完整的工作示例
#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();
}
我有一个 const 引用 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
class 有一个接受 std::tuple
.
template<typename T> explicit Matrix(const T& x)
我不想使用这个构造函数,而且我无法更改它,因为它是第三方代码。
我认为我的 works()
示例正确调用了 cppreference 上列为 #4 的构造函数:
template< class... UTypes > tuple( const tuple<UTypes...>& other );
4) Converting copy-constructor. For all i in
sizeof...(UTypes)
, initializes ith element of the tuple withstd::get<i>(other)
.
fails()
示例尝试使用 this constructor,大概是我不想要的 #3:
template< class... UTypes > explicit tuple( UTypes&&... args );
3) Converting constructor. Initializes each element of the tuple with the corresponding value in
std::forward<Utypes>(args)
.
如何确保 tuple
的构造函数 #4 用于这两种情况?我的真实用例是在可变参数模板中,所以我事先不知道元组的大小。
我会通过在 "source" 元组上调用 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
return类型规范改为std::tuple<no_ref_cv<T>...>
即可。
是的,所以...这就是问题所在:
template<typename T>
explicit Matrix(const T& x)
那是一个非常不友好的构造函数 - 因为它在撒谎。 Matrix
实际上不能从 任何东西 构造,只是一些特定的东西 - 但没有办法从外部检测这些东西是什么。
考虑如何从tuple<Matrix const&>
构造tuple<Matrix>
时,我们有lots of choices,但实际上只有两个可行:
// #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 限定引用),#3 都是首选,解决方案是尝试使用参数 fiddle 以便 # 4 是首选(即通过在参数上使用 as_const()
)。
但是那个构造函数并不是更受欢迎...它实际上 在这里不可行 因为对 that constructor is (from LWG 2549 的限制):
either
sizeof...(Types) != 1
, or (whenTypes...
expands toT
andUTypes...
expands toU
)is_convertible_v<const tuple<U>&, T>
,is_constructible_v<T, const tuple<U>&>
, andis_same_v<T, U>
are allfalse
.
但我们确实有 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
和 fiddle 及其构造函数。
或者,在专门处理大小为 1 的 tuple
时,您可以避免使用元组构造函数,而只使用默认值 construct/assign。或者显式调用 get<0>
或类似的东西。
或者,您可以完全避免 tuple
大小为 1 的文件。这是一件奇怪的具体事情,但也许这就足够了(或者你可以将 tuple
包装成 my_tuple<A, B, C...>
是 tuple<A,B,C...>
但 my_tuple<A>
实际上是 tuple<A, monostate>
).
但真的...修复 Matrix
构造函数似乎非常值得。
如果不有效地重新实现您想要调用的 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)...};
}
};
Demo
使用了一些轻量级的 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);
}
不完全是你问的,但是......通过一个中间模板函数怎么样tplHelper()
,在更通用的情况下,只是return收到的值
template <typename T>
T tplHelper (T const & tpl)
{ return tpl; }
但如果 std::tuple
具有单一类型 return 包含的值 ?
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) };
当你有两个or mote类型时,你继续调用std::tuple
的拷贝构造函数,但是当你用单矩阵的元组调用它时,你避免模板Matrix
构造函数并调用副本 Matrix
构造函数。
以下是完整的工作示例
#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();
}