Search code examples
c++tuplesctad

error: class template placeholder not permitted in this context


The following snippet compiles in gcc 12.1 but not in gcc 11.1. Unfortunately, I only have gcc 11 at hand as I'm crosscompiling for a microcontroller. Is there a way to make it work? Also what is this cryptic "use 'auto' for an abbreviated function template" all about?

Demo

#include <cstdio>
#include <tuple>

template <typename T>
struct channel
{
    int a;
    T b;
};

template <typename... Channels>
struct light
{
    light(const std::tuple<Channels...>& channels)
        :   channels_ { channels }
    {
        
    }

    std::tuple<Channels...> channels_;
};

int main()
{
    light li(std::tuple{channel(2, 2), channel(2,false)});
}

Error (only in gcc 11.1):

<source>:25:14: error: class template placeholder 'std::tuple' not permitted in this context
   25 |     light li(std::tuple{channel(2, 2), channel(2,false)});
      |              ^~~
<source>:25:14: note: use 'auto' for an abbreviated function template

Interestingly, gcc 10.4 has something even different to say:

<source>:25:14: error: 'auto' parameter not permitted in this context
   25 |     light li(std::tuple{channel(2, 2), channel(2,false)});
      |              ^~~

I need a workaround that allows me to not specify all the template parameters of std::tuple as that would blow up my initializer to the point of unrecognizeability :/


Solution

  • I need a workaround ...

    light li{ std::tuple{channel(2, 2), channel(2, false)} };
    //      ^                                              ^
    

    A slightly more involved workaround could be to add deduction guides and constraints.

    #include <cstdio>
    #include <tuple>
    #include <type_traits>
    #include <string>
    
    template <class T>
    struct channel {
        int a;
        T b;
    };
    
    // deduction guide for channel:
    template<class T>
    channel(int, T) -> channel<std::remove_cvref_t<T>>;
    
    // trait to check if a type is a channel
    template<class> struct is_channel : std::false_type {};
    template<class T> struct is_channel<channel<T>> : std::true_type {};
    template<class T> static inline bool is_channel_v = is_channel<T>::value;
    
    // require channel based template parameters
    template <class... Channels>
    requires std::conjunction_v<is_channel<Channels>...>
    struct light {
        template<class... Chs>
        light(Chs&&... channels) : channels_{std::forward<Chs>(channels)...} {}
    
        std::tuple<Channels...> channels_;
    };
    
    // deduction guide for light
    template<class... Channels>
    light(Channels...) -> light<std::remove_cvref_t<Channels>...>;
    

    Now using (...) to initialize the light works even in gcc 11.1:

    int main() {
        channel x{ 2, std::string("Hello") };
    
        light li( channel(2, 2), channel(2, false), x);
    }
    

    Demo