Search code examples
c++gcc

Parameter packs not expanded with '...' c++17 - gcc bug?


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

Solution

  • 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;
    

    Demo and a new bugreport