Consider the following simple code:
template<typename ...types>
class myclass
{
using payload_type = mylib::type_traits::type_or_tuple_t<types...>;
template<typename... T>
std::pair<bool, std::optional<payload_type>> try_push(T &&...data)
{
payload_type t;
std::optional<decltype(t)> o;
// this FAILS.
//std::optional<payload_type> res;
return {true, std::move(o)};
}
};
Above, the type_or_tuple
helper simply converts the variadic pack to either a single type T
if it is a single type, or to a tuple of types (tuple<types...>
) if it is more than one type.
Gcc (9/10/11) cannot compile this. Particularly, it seems is is unable to properly expand payload_type
above, which is of course the type obtained through the type_or_tuple
helper:
g++-11 -O2 -std=c++17 -pedantic wtf.cpp -Wall -Wextra -D_POSIX_SOURCE=200809L -lrt -lmylib -lstdc++ -o q
wtf.cpp:27:70: error: parameter packs not expanded with ‘...’:
27 | std::pair<bool, std::optional<payload_type>> try_push(T &&...data)
| ^
wtf.cpp:27:70: note: ‘T’
Also notice:
payload_type t;
std::optional<decltype(t)> o;
compiles. But the commented line will not.
On the other hand, clang
has no trouble compiling this.
Is this a bug in gcc or is it incorrect usage of the language? Any ideas as to how to tweak this to get it to compile?
Here is a minimum reproducible example, including the definition of is_type_or_tuple
:
#include <iostream>
#include <type_traits>
#include<optional>
#include <tuple>
using namespace std;
template<typename... types>
struct type_or_tuple {
using tup = std::tuple<types...>;
using T = std::conditional_t<(std::tuple_size_v<tup> == 1),
std::tuple_element_t<0, tup>,
tup>;
using is_tuple = std::conditional_t<(std::tuple_size_v<tup> > 1),
std::true_type,
std::false_type>;
};
template<typename... types>
using type_or_tuple_t = typename type_or_tuple<types...>::T;
//
template<typename ...types>
class myclass
{
using payload_type = type_or_tuple_t<types...>;
template<typename... T>
std::pair<bool, std::optional<payload_type>> try_push(T &&...data)
{
payload_type t;
std::optional<decltype(t)> o;
// WILL NOT COMPILE!
//std::optional<payload_type> res;
return {true, std::move(o)};
}
};
int main(int, const char **){
myclass<unsigned> c;
}
It's a bug. gcc confuses the T
in your member function template with the T
in your type trait. A simple change of the trait to use a different name instead of T
, like type
, makes gcc happy too:
template <class... types>
struct type_or_tuple {
using tup = std::tuple<types...>;
using type = std::conditional_t<sizeof...(types) == 1,
std::tuple_element_t<0, tup>, tup>;
};
template <class... types>
using type_or_tuple_t = typename type_or_tuple<types...>::type;