2

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?

Gianluca Bianco
  • 656
  • 2
  • 11

1 Answers1

3

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

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • What if instead I would like to use `ProgressBar` like this: `MultiProgressBar bars(prog_int, prog_double, prog_float);`? The fact is that in `MultiProgressBar` I need then to access a method of `ProgressBar`, from the `bars_` array, in this way: suppose to be in a loop: `bar.get().update( iterating_var );` where `update` comes from the `ProgressBar` class. – Gianluca Bianco Jan 28 '22 at 15:47
  • 1
    @GianlucaBianco I would probably add a `virtual update` in the base class. You can then override that in the derived classes – Ted Lyngmo Jan 28 '22 at 18:11
  • Thanks. However, another problem is that in `ProgressBar ` class the method is defined as `void update( bar_type iterating_var ) ` where `bar_type ` is the type parameter of the template class `ProgressBar `. How could I solve this last problem if `ProgressBarBase ` is not a template? – Gianluca Bianco Jan 28 '22 at 18:37
  • 1
    @GianlucaBianco Aha, ok, that's a bit more complicated, especially with the solution I proposed above since the type information is gone - so using `dynamic_cast` to try to cast to the possible types won't work. Perhaps you should abandon the `std::array` and use a `std::tuple` instead? In that case you don't need the common base class either and can store referrences to the different types. I added examples for that. – Ted Lyngmo Jan 28 '22 at 21:05
  • 1
    The C++11 `MultiProgressBar` could benefit from a `make_MultiProgressBar` function template. – aschepler Jan 28 '22 at 21:28
  • @aschepler Yes, I agree so I added that too. Good idea, thanks! – Ted Lyngmo Jan 28 '22 at 21:51
  • That's fantastic! What if I want to obtain in main something like this: `MultiProgressBar<3> bars( prog_int, prog_double, prog_float );` and then inside three different loop update each one of the bar individually with `bars.update<0>(i)`, `bars.update<1>(i)` etc... (where i is the loop index)? C++17 is fine for this, you can edit only it. PS: I think I will add you to the credits of my project, you helped me a lot @TedLyngmo – Gianluca Bianco Jan 28 '22 at 22:27
  • Another problem that I've noticed is that the code compiles with cland, but with g++ and gcc gives me an error: `error: no matching function for call to ‘std::tuple<>::tuple()’ 63 | MultiProgressBar(Inds&&... bars): bars_{std::forward(bars)...}` – Gianluca Bianco Jan 28 '22 at 23:31
  • 1
    @GianlucaBianco I forgot to `#include ` in the C++11 example which is why it failed. I've updated the answer. Regarding your first question how to update each one individually: You can either call `std::get<0>(bars_).update(i);` if you just want to update the first or you can emulate a loop like I did in `auto dummy = { (std::get(bars_).update(0), 0)... };` - `Ids` can't be a "normal" index variable in a `for` loop since it must be a compile time constant. That's why. – Ted Lyngmo Jan 29 '22 at 07:44
  • 1
    @GianlucaBianco My bad... I had left a `std::ref` in the C++11 example which is why functional was needed. I removed the `std::ref` and `` and added the `` header for `std::forward` instead. It's too early in the morning here :-) – Ted Lyngmo Jan 29 '22 at 08:08
  • Thank you very much! Do you think that it may exist any way to use `std::get (bars_)` where `var ` is an integer parameter of a function? For example to use `void update( var ) ` to update the i’th bar only? – Gianluca Bianco Jan 29 '22 at 12:32
  • 1
    @GianlucaBianco No, not that I can see since template parameters must be known at compile time I'm afraid. – Ted Lyngmo Jan 29 '22 at 12:38
  • Ok. Do you think is there a way to allow something like that, in older to use the update method like: `bars.update(i)` in main, where i is the progress bar number of the bars? – Gianluca Bianco Jan 29 '22 at 13:02
  • 1
    @GianlucaBianco Not direcly but you should be able provide a parameder pack with the aeguments to the individual calls to update. If you use an array as you first did an make the virtual nethod take the same argument type, it is easy though. – Ted Lyngmo Jan 29 '22 at 13:16
  • 1
    @GianlucaBianco Here are some ideas for runtime indexing of tuples that looks pretty nice: https://stackoverflow.com/a/28998020/7582247 – Ted Lyngmo Jan 29 '22 at 15:36
  • @TedLyngmo tha's interesting. Maybe due to some unexperience with this tools I am not so much able to figure it in my example... How my example could be edited with this improvement? Sorry for this many questions, but I am almost learning many C++ features too... – Gianluca Bianco Jan 29 '22 at 15:38
  • 1
    @GianlucaBianco Ok, I added an example. You can now use `bars.for_one()` to apply a functor to a signle tuple element at a time in a loop. – Ted Lyngmo Jan 29 '22 at 16:17
  • @TedLyngmo now it works perfectly! Thank you very much (I will add you to the credits list, you can send me an account you prefer I link if you want). – Gianluca Bianco Jan 29 '22 at 16:30
  • 1
    @GianlucaBianco No worries, I'm just glad it worked out! Cheers! – Ted Lyngmo Jan 29 '22 at 16:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/241521/discussion-between-gianluca-bianco-and-ted-lyngmo). – Gianluca Bianco Jan 29 '22 at 23:00