使用单参数模板化构造函数

时间:2018-04-23 19:08:19

标签: c++ c++11 templates variadic-templates stdtuple

我有一个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类有一个模板化的构造函数,它接受std::tuple

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

我不想使用这个构造函数,因为它是第三方代码所以我无法更改它。

我认为我的works()示例正确调用cppreference上列为#4的构造函数:

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

4)转换copy-constructor。对于sizeof...(UTypes)中的所有i,使用std::get<i>(other)初始化元组的第i个元素。

fails()示例尝试使用this constructor,大概是#3,我不想要这样做:

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

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

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

4 个答案:

答案 0 :(得分:2)

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

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

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

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

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

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

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

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

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

Matrix参数的基础tuple构建其基础Matrix。也就是说,使用转换复制构造函数。似乎问题在于这个构造函数是有效的,但是#3因为任何原因而首选(即它需要较少的cv限定引用)并且解决方案是试图摆弄参数以便#4是首选(即在参数上使用as_const()

但是这个构造函数并不是优先考虑的......这实际上不可行因为对that constructor is的限制(来自LWG 2549):

  

sizeof...(Types) != 1或(Types...扩展为TUTypes...扩展为Uis_­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>真的是{{1} }})。

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

答案 1 :(得分:2)

不完全是你问的问题,但是...通过中间模板函数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) };

当你有两个或者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();
 }

答案 2 :(得分:1)

我通过在&#34;来源&#34;上调用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整数类型):< / p>

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>...>

答案 3 :(得分:1)

如果没有有效地重新实现您想要调用的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);
}