3

I have a tuple of const references std::tuple<const Matrix&, ...> from which I construct a tuple of values std::tuple<Matrix, ...>. For any size of tuple greater than 1, this works fine: (online example: 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};
}

However, for a tuple of size 1, this code fails to compile:

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};
}

Note the Matrix class has a templated constructor which accepts std::tuple.

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

I don't want to use this constructor, and I can't change it since it's third-party code.

I think my works() example properly calls the constructor listed as #4 on cppreference:

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

The fails() example tries to use this constructor, presumably #3, which I don't want:

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

How can I make sure tuple's constructor #4 is used for both cases? My real use case is inside a variadic template so I don't know the size of the tuple in advance.

max66
  • 65,235
  • 10
  • 71
  • 111
Lack
  • 1,625
  • 1
  • 17
  • 29
  • 1
    Your problem is independent of Eigen. If you take one more step and reproduce your problem with an example people can pop into a compiler and reproduce for themselves, you're likely to get more answers because we can get started faster. Bonus points if you use an online compiler we can edit in-place at – AndyG Apr 23 '18 at 19:15
  • https://ideone.com/5I8acU Ideone with a minimal reproducible example for us to work with – Daniel Jour Apr 23 '18 at 19:28
  • 1
    Thanks for the tip, I added a link to godbolt. Here is a modified example without Eigen https://godbolt.org/g/MDG4qU. – Lack Apr 23 '18 at 19:29

4 Answers4

2

Yeah, so... this is the problem:

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

That is a really unfriendly constructor - because it's lying. Matrix isn't actually constructible from anything, just some specific things - but there's no way to externally detect what those things are.

When considering what how to construct a tuple<Matrix> from a tuple<Matrix const&>, we have lots of choices, but actually only two are viable:

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

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

Both end up trying to construct a Matrix from a tuple<Matrix const&>, which doesn't work and you're stuck.


Now, you might think that #4 was your salvation here - generating a constructor that is:

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

And constructing its underlying Matrix from the tuple argument's underlying Matrix. That is, using the converting copy constructor. It seems like the problem is that this constructor works but that #3 is preferred for whatever reason (i.e. that it takes a less cv-qualified reference) and that the solution is to try to fiddle with the arguments so that #4 is preferred (i.e. by using as_const() on the argument).

But that constructor isn't less preferred... it's actually not viable here because the restriction on 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.

But we do have sizeof..(Types) == 1 and those things are not all false (in particular, the second one is true - which is the source of all your problems to begin with), so #4 is simply not a candidate and there isn't really a neat trick to just make it one.


So, how to fix it? The best thing by far to do would to be fix Matrix. I realize that probably isn't likely, but it has to be said.

You could wrap Matrix in something that actually adds constraints to its constructor to avoid this problem. Since you're already copying it into a tuple, that gives you the opportunity to do something even as simple as:

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;
};

but you probably want something little more realistic than this. Also your type could just inherit from Matrix and just fiddle with its constructors for sanity's sake.

Or, when dealing specifically with tuples of size 1, you could avoid using the tuple constructor and just default construct/assign. Or explicitly call get<0> or things of that sort.

Or, you could just avoid tuples of size 1 entirely. Which is a weirdly specific thing to do, but maybe that's enough (or you could wrap tuple in such a way that my_tuple<A, B, C...> is tuple<A,B,C...> but my_tuple<A> is really tuple<A, monostate>).

But really... fixing the Matrix constructor seems just super worthwhile.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks for finding the LWG issue. I noticed that restriction was not there in C++14 standard, which is probably why the code worked for me before with an older compiler. A more general question: is the change retroactive to C++11? Should this be considered a compiler (or stdlib) bug because it breaks existing code compiled with `-std=c++11`, or is this a normal thing for compilers to do because it is fixing a defect? – Lack Apr 23 '18 at 20:12
  • 1
    @Lack I wouldn't think this is a compiler bug. Defects tend to get applied retroactively, given that they are defects. I don't know what the hard and fast rule here is. – Barry Apr 23 '18 at 20:20
2

Not exactly what you asked but... what about passing through a intermediate template function tplHelper() that, in the more generic case, simply return the value received

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

but in case of a std::tuple with a single type return the contained value ?

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

If you create t passing through tplHelper()

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

// ...

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

when you have two or mote types, you continue to call the copy constructor of std::tuple, but when you call it with a tuple with a single matrix, you avoid the template Matrix constructor and call the copy Matrix constructor.

The following is a full working example

#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();
 }
max66
  • 65,235
  • 10
  • 71
  • 111
1

I'd circumvent this issue by calling std::get on the "source" tuple:

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

This avoids calling the explicit constructor and instead calls the copy constructor.

In order to generalize this for tuples of any length you can make use of std::integer_sequence and what builds upon it and wrap this all up in a function:

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...>{});
}

Stuck with C++11?

std::integer_sequence and it's friends can be written in C++11, too (not a full replacement, misses member function for example and probably breaks with signed integer types):

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)>;

Then you only need to change the auto return type specifications of the two functions to std::tuple<no_ref_cv<T>...>.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • Thanks, I accepted the other answer for the full explanation referring to the standard, but realistically using `std::get` is probably the simplest solution. (It just requires making a partial specialization of my variadic template for the case size==1.) – Lack Apr 23 '18 at 20:18
1

I couldn't think of a good solution to the problem without effectively reimplementing the tuple constructor you wanted to be invoked:

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

Uses some light C++14, but nothing that you cannot implement in C++11

Effectively we use index sequences to call std::get ourselves. Given some garbage implementation of Matrix like so:

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

Your fails test now passes:

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);
}
AndyG
  • 39,700
  • 8
  • 109
  • 143