2

So, as I've been learning about templates in C++, I decided to come up with some unusual situations to see if I could get them to work. (No, it's not practical - just to play with the language!) I made a template class that holds a value of type T, with a variadic function template that returns a std::pair of T and the maximum of one of the values in the parameter pack. However, I can't get it to compile. Here's what I wrote...

In header.h:

#ifndef HEADER_H
#define HEADER_H

#include <utility>
#include <algorithm>
#include <array>

template <typename  T>
class MyClass
{

  T m_heldType;

public:

  MyClass(T t) : m_heldType(t) {}

  template <typename... T2>
  std::pair<T, T2> pairingFunc(T2&&... types)
  {
    std::array<T2, sizeof...(types)> types_arr = { types... };

    return std::make_pair( m_heldType, *std::max_element(types_arr.begin(), types_arr.end()) );
  }

};

#endif

In source.cpp:

#include <iostream>
#include "Header.h"

int main()
{
  MyClass<int> mc(512);

  auto mypair = mc.pairingFunc(1.61f, 3.14f, 6.66f);

  std::cout << mypair.first << ' ' << mypair.second;
  std::cin.ignore();
}

These are the errors I generate:

Error   C3520   'T2': parameter pack must be expanded in this context   ...\header.h    24
Error   C2672   'MyClass<int>::pairingFunc': no matching overloaded function found  ...\source.cpp  8
Error   C2893   Failed to specialize function template 'std::pair<T,T2> MyClass<T>::pairingFunc(T2 &&...)'  ...\source.cpp  8
Error   C3536   'mypair': cannot be used before it is initialized   ...\source.cpp  10
Error   C2228   left of '.first' must have class/struct/union   ...\source.cpp  10
Error   C2228   left of '.second' must have class/struct/union  ...\source.cpp  10

So here's what I'm thinking:

  • Obviously, the compiler can't determine the type of mypair (fails to initialize). But why? It knows the type of T in MyClass and the type of T2 in pairingFunc(). Explicitly stating std::pair<int, float> fixes this error, but leaves the others (a symptom of the underlying issue).
  • "Failed to specialize function template"...which I guess means that it couldn't return the type based on the types given? If so, why not?
  • "parameter pack must be expanded in this context" - I'm not sure about this one. Doesn't that occur by putting the pack into an array?

Additionally, I'd like to enforce provision of at least one argument through something like (T2&& head, T2&&... tail), but I think getting both of those into an array or vector could be nasty, and I'm not sure how to deal with just the single variadic as it is. So that's just a 'bonus' I guess.

2 Answers2

2

The fundamental problem is that a parameter pack

<typename ...T2>

does not represent a single type.

 template <typename... T2>
 void foo(T2 && ...args)

This parameter pack can accept a combination of types, i.e.

 foo(4, "bar", Baz());

You need to figure out the type of the parameters that pairingFunc() receives. The suggested std::common_type_t is a good choice:

template <typename  T>
class MyClass
{

    T m_heldType;

public:

    MyClass(T t) : m_heldType(t) {}

    template <typename ...T2>
    auto pairingFunc(T2 &&...values)
    {
        std::array<std::common_type_t<T2...>, sizeof...(T2)> types_arr = { values... };

        return std::make_pair( m_heldType, *std::max_element(types_arr.begin(), types_arr.end()) );
    }

};
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Thank you, this answer is good as well, but I'm still accepting the other one for using a method signature that requires at least one arg, and without use of `std::common_type`. But now that I know about it I can add it to my arsenal, so to speak. – The Eyesight Dim Aug 27 '16 at 15:46
  • 1
    Without using `std::common_type`, the type of the values is forced to match the first parameter's. I.e. pairingFunc(0, 1.2f, 3.4f) will generate a compiler warning, since the internal `std::array` will contain `int`s, instead of floats. – Sam Varshavchik Aug 27 '16 at 15:48
  • So, `std::common type` considers the implicit conversion of the following types as well? I guess it could be more or less desirable, depending on the context (working with literals I'd prefer to explicitly say `f` or `l` or so forth), but again, still useful, somewhere. – The Eyesight Dim Aug 27 '16 at 15:51
2

The problem is here:

std::pair<T, T2> pairingFunc(T2&&... types)
             ^^

and here

std::array<T2, sizeof...(types)> types_arr = { types... };
           ^^

T2 is a parameter pack, not a type. It can store multiple types, i.e. 1.4f, "hi", 1, 0.5. So you cannot use it as a single type, it's just not possible. You need to introduce another parameter and use that as a type:

template <typename T1, typename... T2>
std::pair<T, T1> pairingFunc(T1&& arg, T2&&... types)
{
    std::array<T1, sizeof...(types) + 1> types_arr = { arg, types... };

    return std::make_pair(m_heldType, *std::max_element(types_arr.begin(), types_arr.end()));
}

This also has the advantage that if you call

mc.pairingFunc(4.5f, "hello");

it wouldn't compile. Calling it with no arguments is also not possible anymore (which wouldn't make sense nonetheless).


A preferable solution (thanks @DanielSchepler) might be to use std::common_type, as it may be possible that the second argument is not convertible to T, but T is convertible to the second argument:

template <typename T1, typename... T2>
std::pair<T, std::common_type_t<T1, T2...>> pairingFunc(T1&& arg, T2&&... types)
{
    std::array<std::common_type_t<T1, T2...>, sizeof...(types) + 1> types_arr = { arg, types... };

    return std::make_pair(m_heldType, *std::max_element(types_arr.begin(), types_arr.end()));
}
Community
  • 1
  • 1
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 2
    Or, it might be appropriate to use `std::common_type_t` in the type of the array and the second member of the return type. – Daniel Schepler Aug 27 '16 at 15:38
  • 1
    @DanielSchepler Good idea, didn't know about `std::common_type`, thanks :) – Rakete1111 Aug 27 '16 at 15:39
  • @Rakete1111 Thank you for your answer! I assumed that referring to `T2` would just mean 'an array of T2's type' and not the entire parameter pack. And it means I was getting close with the (head, tail...) thinking. Plus it adds in the 'must have at least one arg' restriction, and single typing. Again, thank you, this was a clear and to the point answer. – The Eyesight Dim Aug 27 '16 at 15:40
  • @Daniel I didn't know about `std::common_type` either, thank you, I'll look it up! – The Eyesight Dim Aug 27 '16 at 15:42