尝试使用特定模板 class 参数获取 std::reference_wrapper 时出现问题

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

我正在努力解决一个我无法解决的问题。我搜索了整个网站和网络,但没有找到任何解决方案(可能是因为我不太了解某些事情,所以我无法正确提问)。

问题如下,假设我有一个带有一些参数的模板class,其中一个是模板本身,定义在class.h 文件:

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

在我的 main.cpp 文件中,我希望能够执行如下操作:

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

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

其中 ProgressBar 是在另一个 header 中定义的模板 class,例如 progress.h

问题是当我编译时,我得到了这个错误(我没有 post 整个 makefile 因为它真的很复杂而且解释起来很长,但是让我们考虑到我正在编译更多内容,但此错误中唯一重要的错误在 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

你知道我的代码有什么问题吗?

对于存储引用的元素,您也需要一个公共基础。这可能是一种方式:

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

这样,您可以稍微更改 class 以存储对 ProgressBarBase 的引用(或您想要的任何指标基础 class)。

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

但是,如果您有一个函数,例如 ProgressBar 中的 void update(bar_type value);,那么在基 class 中将其设为 virtual 将不起作用(因为 bar_type 在基础 class) 中未知。

一种选择是删除 std::array 并改用 std::tuple。这使得保留类型信息成为可能,并且还摆脱了对基数 class 的需要。您也不需要 reference_wrapper 因为引用不会存储在数组中。

C++17 示例:

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

直到我完成上面的操作,我才注意到 C++11 标签。这也是一个 C++11 示例。实施起来还有很多,但我现在想不出更简单的方法:

#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

对于 C++11,您可以通过提供包含您要为元组中的所有元素调用的函数的仿函数来使其更简单:

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

仿函数可能如下所示:

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

并像这样使用:

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