Reputation: 786
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
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...>;
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';
}
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';
}
Upvotes: 3