Gianluca Bianco
Gianluca Bianco

Reputation: 786

Problem when trying to obtain an std::reference_wrapper with a particular template class parameter

I am struggling with a problem that I am not able to solve. I searched all around the website and web in general and didn't find any solution (maybe this is due to the fact that I doesn't understand well something and I am not able to ask it correctly).

The problem is the following, supposing I have a template class with some parameters, whose one of them is a tempate itself, defined in a class.h file:

template <template <typename bar_type> typename Indicator, size_t count>
class MultiProgressBar
 {
  public:

   template <typename... Indicators, typename = typename std::enable_if_t <(sizeof...( Indicators ) == count )>>
   explicit MultiProgressBar( Indicators &... bars ) : bars_( { bars... } ) {}
 
  private:
   
   std::array <std::reference_wrapper<Indicator>, count> bars_;
 };

In my main.cpp file I want to be able to do something like this:

ProgressBar<int> prog_int;
ProgressBar<double> prog_double;
ProgressBar<float> prog_float;

MultiProgressBar <ProgressBar, 3> bars( prog_int, prog_double, prog_float );

where ProgressBar is a template class defined in another header called for example progress.h

The problem is that when I compile, I got this error (I don't post the whole makefile since it would be really complex and long to explain, however let's take into account that I am compiling more stuff, but the only significant one for this error is given in this post):

g++ -g -Isrc -MMD -MP  -c src/main.cpp -o obj/src/main.cpp.o
In file included from src/../include/osmanip.h:7,
                 from src/main.cpp:6:
src/../include/multi_progress_bar.h:48:50: error: type/value mismatch at argument 1 in template parameter list for ‘template<class _Tp> class std::reference_wrapper’
   48 |      std::array <std::reference_wrapper<Indicator>, count> bars_;
      |                                                  ^
src/../include/multi_progress_bar.h:48:50: note:   expected a type, got ‘Indicator’
src/../include/multi_progress_bar.h:48:58: error: template argument 1 is invalid
   48 |      std::array <std::reference_wrapper<Indicator>, count> bars_;
      |                                                          ^
src/../include/multi_progress_bar.h: In instantiation of ‘osm::MultiProgressBar<Indicator, count>::MultiProgressBar(Indicators& ...) [with Indicators = {osm::ProgressBar<int>, osm::ProgressBar<double>, osm::ProgressBar<float>}; <template-parameter-2-2> = void; Indicator = osm::ProgressBar; long unsigned int count = 3]’:
src/main.cpp:245:77:   required from here
src/../include/multi_progress_bar.h:23:77: warning: list-initializer for non-class type must not be parenthesized
   23 |      explicit MultiProgressBar( Indicators &... bars ) : bars_( { bars... } ) {}
      |                                                                             ^
src/../include/multi_progress_bar.h:23:77: error: cannot convert ‘<brace-enclosed initializer list>’ to ‘int’ in initialization
make: *** [makefile:65: obj/src/main.cpp.o] Error 1

Do you know what could be wrong with my code?

Upvotes: 2

Views: 276

Answers (1)

Ted Lyngmo
Ted Lyngmo

Reputation: 117318

You'll need a common base for the elements you store references too. This could be one way:

struct ProgressBarBase {
    // Optional: A virtual destructor if you want to delete objects via
    // base class pointers:
    virtual ~ProgressBarBase() = default;
};

template <class bar_type>
struct ProgressBar : public ProgressBarBase {};

With that, you could change your class slightly to store references to ProgressBarBase (or whatever indicator base class you'd like).

template <class T, std::size_t count>
class MultiProgressBar {
public:
    template<class... Indicators, std::enable_if_t<sizeof...(Indicators) == count, int> = 0>
    MultiProgressBar(Indicators&&... bars)
        : bars_{std::forward<Indicators>(bars)...}
    {
        // or this instead of SFINAE:
        static_assert(sizeof...(Indicators) == count, "wrong number of arguments");
    }

private:
    std::array<std::reference_wrapper<T>, count> bars_;
};

int main() {
    ProgressBar<int> prog_int;
    ProgressBar<double> prog_double;
    ProgressBar<float> prog_float;

    MultiProgressBar<ProgressBarBase, 3> bars(prog_int, prog_double, prog_float);
}

However, if you for example have a function, like void update(bar_type value); in ProgressBar, making it virtual in the base class will not work (since bar_type is not known in the base class).

One option could be to drop the std::array and use a std::tuple instead. This makes it possible to keep the type information and it also gets rid of the need for the a base class. You also do not need the reference_wrapper since the references won't be stored in an array.

C++17 example:

template <class bar_type>
struct ProgressBar{
    void update(bar_type v) {
        std::cout << value << '\n';
        value = v;     
    }

    bar_type value;
};

template <class... Indicators>
class MultiProgressBar {
public:
    template<class... Inds>
    MultiProgressBar(Inds&&... bars) : bars_{std::forward<Inds>(bars)...} {}

    void update() {
        std::apply([](auto&... rw){
            (rw.update(0), ...);
        }, bars_);
    }

private:
    std::tuple<Indicators&...> bars_;
};

// deduction guide
template<class... Indicators>
MultiProgressBar(Indicators...) -> MultiProgressBar<Indicators...>;

C++17 Demo

I didn't notice the C++11 tag until I had finished the above. Here's a C++11 example too. It's a lot more to implement but I couldn't think of an easier way to do it right now:

#include <iostream>
#include <tuple>
#include <utility>

// A type to generate indices for parameter packs:
template<size_t... Is>
struct indices { };

template<size_t N, size_t... Is>
struct gen_indices : gen_indices<N - 1, N - 1, Is...> { };

template<size_t... Is>
struct gen_indices<0, Is...> : indices<Is...> { };

template <class bar_type>
struct ProgressBar{
    void update(bar_type v) {
        std::cout << value << '\n';
        value = v;     
    }

    bar_type value;
};

template <class... Indicators>
class MultiProgressBar {
public:
    template<class... Inds>
    MultiProgressBar(Inds&&... bars) : bars_{std::forward<Inds>(bars)...} {}

    void update() {
        // call the update overload what takes an indices<size_t...>
        update(gen_indices<sizeof...(Indicators)>());
    }

private:
    template<size_t... Ids>
    void update(indices<Ids...>) {
        // call update on every element in the tuple
        // , 0 is done to discard the `void` return from `update`
        // to build a dummy initializer list (since C++11 lacks fold expressions)
        auto dummy = { (std::get<Ids>(bars_).update(0), 0)... };
        (void) dummy; // quiet warning about unused variable
    }

    std::tuple<Indicators&...> bars_;
};

// Since deduction guides doesn't exist in C++11, we'll add a helper function:
template<class... Indicators>
MultiProgressBar<typename std::remove_reference<Indicators>::type...>
make_MultiProgressBar(Indicators&&... inds) {
    return {std::forward<Indicators>(inds)...};
}

int main() {
    ProgressBar<int> prog_int{1};
    ProgressBar<double> prog_double{2};
    ProgressBar<float> prog_float{3};

    auto bars = make_MultiProgressBar(prog_int, prog_double, prog_float);
    bars.update();

    // all set to 0:
    std::cout << prog_int.value << prog_double.value << prog_float.value << '\n';
}

C++11 Demo

For C++11 you could make it simpler by making it possible to provide functors containing the function you want to call for all elements in the tuple:

template <class... Indicators>
class MultiProgressBar {
public:
    template<class... Inds>
    MultiProgressBar(Inds&&... bars) : bars_{std::forward<Inds>(bars)...} {}

    static size_t size() { return sizeof...(Indicators); }

    template <class Func, class... Args>
    void for_one(size_t idx, Func&& func, Args&&... args) {
        call_one(idx, gen_indices<sizeof...(Indicators)>(),
                 std::forward<Func>(func), std::forward<Args>(args)...);
    }

    template<class Func, class... Args>
    void for_each(Func func, Args&&... args) {
        // call `call_all` that takes an indices<size_t...>
        // with a function and the arguments to pass to the function
        call_all(gen_indices<sizeof...(Indicators)>(), func, std::forward<Args>(args)...);
    }

private:
    template <size_t... Ids, class Func, class... Args>
    void call_one(size_t idx, indices<Ids...>, Func&& func, Args&&... args) {
        [](...) {} ( // define a dummy lambda that takes any arguments and call it
            // with the below as arguments. Short-circuit evaluation makes sure
            // that `func` only gets called when `idx == Ids`
            (idx == Ids && // (void) below to avoid warnings about unused return vals
                     ((void)std::forward<Func>(func)(
                          std::get<Ids>(bars_), std::forward<Args>(args)...),
                      false))...
        );   
    }

    template<size_t... Ids, class Func, class... Args>
    void call_all(indices<Ids...>, Func&& func, Args&&... args) {
        // call `func` with every element in the tuple
        // ", 0" is done to discard the `void` return from `update`
        // to build a dummy initializer list (since C++11 lacks fold expressions)
        auto dummy = { (func(std::get<Ids>(bars_), args...), 0)... };
        (void) dummy; // quiet warning about unused variable
    }

    std::tuple<Indicators&...> bars_;
};

A functor could then look like this:

template< class T > // borrowed from c++20
struct type_identity { using type = T; };

struct updater { // instead of a lambda with auto as argument type
    template<template<class> class PB, class bar_type>
    auto operator()(PB<bar_type>& pb, 
                    typename type_identity<bar_type>::type v) const -> decltype(pb.update(bar_type{}))
    {
        return pb.update(v);
    }
};

and be used like this:

int main() {
    ProgressBar<int> prog_int{1};
    ProgressBar<double> prog_double{2};
    ProgressBar<float> prog_float{3};

    auto bars = make_MultiProgressBar(prog_int, prog_double, prog_float);
    bars.for_each(updater{}, 4);

    // all set to 4:
    std::cout << prog_int.value << '\n'
              << prog_double.value << '\n'
              << prog_float.value << '\n';

    for(size_t i = 0; i < bars.size(); ++i) {
        bars.for_one(i, updater{}, i);
    }

    // 0, 1 and 2
    std::cout << prog_int.value << '\n'
              << prog_double.value << '\n'
              << prog_float.value << '\n';    
}

Demo

Upvotes: 3

Related Questions