Search code examples
c++templates

Interpreting C++ variadic template arguments from other variadic template arguments


I would appreciate if someone is able to supply me with better terminology for the question as I am struggling to put it into words in the title. Essentially, my problem is I require a struct along the lines of

template <typename... Types>
struct acceptor {

  // ...

  template <
    // assumedly, something needs to go here
  >
  static inline void accept(void) { 
    // ...
  }

  // ...

};

such that I am able to run

int main(void) {
  acceptor<int, int, float>::template accept<0, 1, 0.5f>();
  acceptor<unsigned long, double>::template accept<6UL, -4.6>();

  return 0;
}

Can I please get a definitive answer on whether or not this is possible within the C++ language and if so, how?

I tried

template <
  Types... Values
>
static inline void accept(void) {
 // ...
}

but that just seemingly tried to make essentially a variadic of the first type rather than a single non-type parameter per outer argument. The argument appears to always be wrong number of template arguments (<whatever number of arguments I specified>, should be 1).

It does not appear to be possible in C++20 or any later version from what I can tell and I've had to construct a structural (so it can be used at compile time) tuple type to handle this but it is somewhat ugly and I'd like to avoid using it if possible. Noting that this was being done on an Ubuntu install of gcc.


Solution

  • In conjunction with the similar answers provided by @TedLyngmo (https://stackoverflow.com/a/79464046/28414083) and @Jarod42 (https://stackoverflow.com/a/79464150/28414083), the accepted form of solution for the specific question asked would be:

    #include <type_traits>
    
    template <class... Types>
    struct acceptor {
      template <auto... Values>
        requires std::conjunction_v<std::is_same<Types, decltype(Values)>...>
      static inline void accept(void) {
        // ...
      }
    };
    

    as this compiles in GCC. Unfortunately, this obviously destroys an IDE's ability to use any kind of "intellisense". It also requires you to specify the values, even as literals, in their specific types, meaning that the literal 1, for example, would not work for an input of a long's corresponding template non-type parameter as the 1 literal is considered an int by the compiler. This could be beaten by changing std::is_same to std::is_trivially_constructible in the requirement clause or similar but it's recommended, where possible, to change to something like:

    #include <type_traits>
    
    template <class... Types>
    struct acceptor {
      template <Types... Values>
      struct internal {
        static inline void accept(void) {
          // ...
        }
      };
    };
    

    which has an internal temploid struct that, unlike its internal temploid function counterpart, is able to compile with that template in GCC. This changes the format of the expected use to:

    int main(void) {
      // allows '1U' specified for 'int'
      acceptor<signed char, int, float>::template internal<0, 1U, 0.5f>::accept();
      // allows '6' specified for 'unsigned long'
      acceptor<unsigned long, double>::template internal<6, -4.6>::accept();
    
      return 0;
    }
    

    Tested with gcc version: Ubuntu 13.2.0-4ubuntu3.