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:
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).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.
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()));
}