Search code examples
c++c++11variadic-templatestemplate-meta-programmingstdtuple

No matching constructor for initialization of variadic template class


I am trying to write a wrapper class to a Cartesian product iterator provided by Miranda Conrado (source code can be found on GitHub). For convenience I will quote relevant bits of code here, too.

My class can be constructed in two ways - one straightforward, by just forwarding the Containers to the product_iterator constructor, the other is a bit trickier: it takes a number of tuples describing the linspace needed to create the Containers and then constructs the iterator out of them. It is here where I hit a dead-end.

Here is some code. First, some relevant headers from Conrado's class product_iterator:

// product_iterator.hpp

template <class... Containers>
class product_iterator:
    ...

    public:
      product_iterator();

      product_iterator(product_iterator const& other);

      product_iterator(Containers const&... containers);

      ~product_iterator();

      product_iterator const& operator=(product_iterator const& other);

     ....
};

template <class... Containers>
product_iterator<Containers...>
make_product_iterator(Containers const&... containers) {
  return product_iterator<Containers...>(containers...);
}

And here is my class:

// gridsearch.hpp

typedef std::unordered_map<std::string, Real> result_type;
typedef std::vector<result_type> resultgrid_type;


template <class... Containers>
class GridSearchIterator {
    typedef std::array<std::string,
            std::tuple_size<std::tuple<Containers...> >::value> 
            argname_type;

public:
    GridSearchIterator() : product_it(product_iterator<Containers...>()), 
                           argnames(argname_type()) {}

    GridSearchIterator(const argname_type& names, 
                       const Containers& ...containers);

    template <class... TupleTypes>
    static GridSearchIterator<Containers...> 
                                    initWith(const TupleTypes&& ...tuples);

    template<class F, class... Args>
    decltype(auto) iterate(F func, Args&&... params);

private:
    template <typename TupleType, size_t... Is>
    void product_impl(TupleType&& tuples, std::index_sequence<Is...>);
    template <typename TupleType>
    const auto& unpack_tuple(TupleType& t, size_t index);

    product_iterator<Containers...> product_it;
    argname_type argnames;
};

// implementation:

template <class... Containers>
GridSearchIterator<Containers...>::GridSearchIterator(
                                          const argname_type& names, 
                                          const Containers& ...containers):

                product_it(product_iterator<Containers...>(containers...)),
                                                        argnames(names) {}

template <class... Containers>
template <typename... TupleTypes>
GridSearchIterator<Containers...> GridSearchIterator<Containers...>::initWith(const TupleTypes&& ...tuples) 
{
    GridSearchIterator<Containers...> gsi = 
                                       GridSearchIterator<Containers...>();
    gsi.product_impl(std::tuple<TupleTypes...>(tuples...), 
                     std::index_sequence_for<TupleTypes...>{});
    return gsi;
}

template <class... Containers>
template <typename TupleType, size_t... Is>
void GridSearchIterator<Containers...>::product_impl(TupleType&& tuples, 
                                              std::index_sequence<Is...>) 
{
    product_it = product_iterator<Containers...>(
                                unpack_tuple(std::get<Is>(tuples), Is)...); 
// this is where the problem is; Compiler claims No matching constructor for initialization of 'product_iterator...
}

template <class... Containers>
template <typename TupleType>
const auto& GridSearchIterator<Containers...>::unpack_tuple(TupleType &t, 
                                                            size_t index) 
{
    std::string argname;
    auto left(0), right(0);
    Size step;
    std::tie(argname, left, right, step) = t;
    argnames[index] = argname;
    auto vec = linspace(left, right, step);
    return static_cast<const decltype(vec) &>(vec);
}

The function linspace above returns a vector of numbers from left to right evenly spaced by number of steps. It is equivalent to Numpy function np.linspace.

I checked and the call to unpack_tuple() does produce the vectors needed to initialise the product_iterator, yet the compiler disagrees. My guess is the types returned by unpack_tuple() are somewhat different from what the product_iterator constructor expects but I can't figure out what is the problem. Or maybe the problem actually lies elsewhere entirely.

For better understanding, here is how I use the class:

{
...
    typedef std::tuple<std::string, int, int, size_t> inttuple;
    typedef std::tuple<std::string, double, double, size_t> realtuple;
    typedef std::vector<int> intvector;
    typedef std::vector<Real> realvector;

    inttuple sidespan = std::make_tuple("side",1,1,1);
    real tuple takeprofit = std::make_tuple("takeprofit",1.,2.,2);
    real tuple stoploss = std::make_tuple("stoploss", -1.,-3.,3);
    inttuple period = std::make_tuple("horizon", 100, 100, 1);

    auto grid_iter = GridSearchIterator<intvector, realvector, realvector, intvector>
                                        ::initWith(std::forward<inttuple>(sidespan),
                                                   std::forward<realtuple>(takeprofit),
                                                   std::forward<realtuple>(stoploss),
                                                   std::forward<inttuple>(period));
...
}

I spent hours trying to solve it so any help or pointers will be highly appreciated, including advice on different implementation.

UPDATE
Sorry, I thought I updated my question yesterday but the changes were not saved for some reason. Anyway, @max66 answered the question even without additional info. Still, for completeness, here is the linspace() definition

template <typename T>
std::vector<T> linspace(T a, T b, size_t N)

and compiler message:

In file included from /.../main.cpp:17:
/.../gridsearch.hpp:98:18: error: no matching constructor for initialization of 'product_iterator<std::__1::vector<int, std::__1::allocator<int> >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<int, std::__1::allocator<int> > >' product_it = product_iterator<Containers...>(unpack_tuple(std::get<Is>(tuples), Is)...);

/.../gridsearch.hpp:91:9: note: in instantiation of function template specialization 'GridSearchIterator<std::__1::vector<int, std::__1::allocator<int> >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<int, std::__1::allocator<int> > >::product_impl<std::__1::tuple<std::__1::tuple<std::__1::basic_string<char>, int, int, unsigned long>, std::__1::tuple<std::__1::basic_string<char>, double, double, unsigned long>, std::__1::tuple<std::__1::basic_string<char>, double, double, unsigned long>, std::__1::tuple<std::__1::basic_string<char>, int, int, unsigned long> >, 0, 1, 2, 3>' requested here gsi.product_impl(std::tuple<TupleTypes...>(tuples...), std::index_sequence_for<TupleTypes...>{});

/.../main.cpp:90:88: note: in instantiation of function template specialization 'GridSearchIterator<std::__1::vector<int, std::__1::allocator<int> >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<double, std::__1::allocator<double> >, std::__1::vector<int, std::__1::allocator<int> > >::initWith<std::__1::tuple<std::__1::basic_string<char>, int, int, unsigned long>, std::__1::tuple<std::__1::basic_string<char>, double, double, unsigned long>, std::__1::tuple<std::__1::basic_string<char>, double, double, unsigned long>, std::__1::tuple<std::__1::basic_string<char>, int, int, unsigned long> >' requested here auto grid_iter = GridSearchIterator<intvector, realvector, realvector, intvector>::initWith(std::forward<inttuple>(sidespan),

In file included from /.../main.cpp:17:
In file included from /.../gridsearch.hpp:22: /.../product_iterator.hpp:73:7: note: candidate constructor not viable: no known conversion from 'vector<int, allocator<int>>' to 'const vector<double, allocator<double>>' for 2nd argument product_iterator(Containers const&... containers);


Solution

  • It's difficult to check/verify/propose a correct code if you don't propose a complete example.

    Anyway you have the error ("no matching constructor") in this line

        product_it = product_iterator<Containers...>(
                                    unpack_tuple(std::get<Is>(tuples), Is)...); 
    

    where, if I understand correctly, Containers... is intvector, realvector, realvector, intvector a.k.a. std::vector<int>, std::vector<Real>, std::vector<Real>, std::vector<int> (where I suppose Real is an alias for double).

    The only variadic constructor for product_iterator is the one receiving Containers const&... containers so I suppose is the one you want to match.

    It seems to me that the problem is that unpack_tuple()

    template <class... Containers>
    template <typename TupleType>
    const auto& GridSearchIterator<Containers...>::unpack_tuple(TupleType &t, 
                                                                size_t index) 
    {
        std::string argname;
        auto left(0), right(0);
        Size step;
        std::tie(argname, left, right, step) = t;
        argnames[index] = argname;
        auto vec = linspace(left, right, step);
        return static_cast<const decltype(vec) &>(vec);
    }
    

    return ever a intVector const & (std::vector<int> const &). Also when called with a realVector (std::vector<double>, I suppose).

    This (if I'm not wrong) it's because you define left and right as auto and initializing they whit an int

    auto left(0), right(0);
    

    you get a couple of int also when TupleType contains Real elements in second and third position.

    So when you obtain vec

    auto vec = linspace(left, right, step);
    

    you obtain (I suppose) a std::vector<int>; ever; also when you should obtain a std::vector<Real>.

    Suggestion: define left and right with the correct type that depends from TupleType.

    By example (caution: code not tested)

    using lr_type = typename std::tuple_element<1u, TupleType>::type;
    
    lr_type  left, right;
    

    Starting from C++14 you can use std::tuple_element_t and the `using can be simplified as follows

    using lr_type = std::tuple_element_t<1u, TupleType>;
    

    If you can use C++17, you can use structured binding and all become a lot simpler

    template <typename TupleType>
    const auto& GridSearchIterator<Containers...>::unpack_tuple(TupleType &t, 
                                                                size_t index) 
    {
        auto [argname, left, right, step] = t;
        argnames[index] = argname;
        auto vec = linspace(left, right, step);
        return static_cast<const decltype(vec) &>(vec);
    }
    

    Off Topic: are you sure that is a good idea unpack_tuple() returning a const reference to a value that is destroyed at the end of execution of the method ?