Variadic templates with forward reference and operator +=

627 Views Asked by At

I have function which should taken several parameters and after add all parameters to *this, operator+= is overloaded and work for one parameter. Function look like

template <typename T, typename U>
struct are_equal :
    std::is_same<typename std::decay<T>::type, U>::type
{};

template<typename T>
template<typename... U>
std::enable_if_t<are_equal<U ..., Object<T>>::value>
Object<T>::addd(U &&... u)
{
//  *this += std::forward<U>(u)...;
}

Probably i have two problems. 1. I think that i should change part of my code with type_traits, but my experiments don't give correctly result. Error with comments line (where is operator +=)

C2893 Failed to specialize function template 'enable_if<_Test,_Ty>::type Object::addd(U &&...)' C2672 'Object::addd': no matching overloaded function found

  1. Problem with dictionary(?) in comment line:

    *this += std::forward<U>(u)...;
    

Error:

C2143 syntax error: missing ';' before '...'

C2059 syntax error: '...'

C3520 'u': parameter pack must be expanded in this context

Operator+= work fine for one element (I'm sure).

2

There are 2 best solutions below

1
On

I think you want the following:

template <bool ... Bs> struct bools {};

template <bool ... Bs>
using all_of = std::is_same<bools<true, Bs...>, bools<Bs..., true>>;

template <typename T, typename ... Ts>
using are_equal = all_of<std::is_same<T, Ts>::value...>;

template<typename T>
template<typename... Us>
std::enable_if_t<are_equal<T, std::decay_t<Us>...>::value>
Object<T>::add(Us&&... u)
{
    const int dummy[] = {0, ((*this += std::forward<Us>(u)), 0)...};
    static_cast<void>(dummy);
}
4
On

I assume you want that pack expansion to produce something like this:

*this += std::forward<U>(u_1),
*this += std::forward<U>(u_2),
// ...
*this += std::forward<U>(u_n);

The reason why *this += std::forward<U>(u)...; doesn't work is that, roughly speaking, commas produced by a pack expansion (but not by a fold expression, see below) can't be used as operators.

The classical pre-C++17 workaround is to use a dummy array:

using dummy_array = int[];
dummy_array{(*this += std::forward<U>(u), 0)..., 0};

Note that commas produced by this expansion aren't used as operators (but rather as separators of element initializers), thus the above restriction isn't applied.

The first , 0 in the above snippet lets us ignore the return type of *this += blah.
The second , 0 is used to support empty parameter packs (otherwise there would be an attempt to create an empty array, which is not allowed).

The type alias is necessary because the compiler wouldn't let you use int[]{blah, blah} directly.

As an alternative to using, you could use something like

std::enable_if_t<1, int[]>{(std::cout << p, 0)..., 0};

Another option would be to create an actual array instead of temporary one:

int dummy_array[]{(std::cout << p, 0)..., 0};
(void)dummy_array;

But I don't like this one too much.


If you have C++17, you should use fold expressions instead:

((*this += std::forward<U>(u)), ...);