admin管理员组

文章数量:1277910

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.

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.

Share Improve this question edited Feb 25 at 5:49 notgapriel asked Feb 24 at 14:54 notgaprielnotgapriel 1111 silver badge5 bronze badges 10
  • 2 What is _Types used for? Note that it's a reserved name and you shouldn't use those and void main(void) should be int main(). – Ted Lyngmo Commented Feb 24 at 15:11
  • 1 Unless I overlooked your issue, I think you are overcomplicating things, maybe you wanted something like: godbolt./z/TMh9hGKMM ? – Fareanor Commented Feb 24 at 15:23
  • 1 [Not the Issue] void main(void) is incorrect C++ code. main always returns an int. – NathanOliver Commented Feb 24 at 15:27
  • 2 @NathanOliver 13.2.17 third definition in Example 10 seems to be very similar to OP's setup, except it's an inner class template instead of a function template. – Ted Lyngmo Commented Feb 24 at 16:04
  • 2 @TedLyngmo Yeah, that is basically the same. I guess the compilers have yet to catch up. – NathanOliver Commented Feb 24 at 16:08
 |  Show 5 more comments

3 Answers 3

Reset to default 9

There seems to be a bug in gcc and MSVC preventing the instantiation of accept using Types... values, but you can work around it by using auto...:

#include <type_traits>

template <class... Types>
struct acceptor {
    template <auto... values>
//            ^^^^^^^
        requires std::conjunction_v<std::is_same<Types, decltype(values)>...>
    static void accept() {
        // use values as you want:
        (..., (std::cout << ' ' << values));
        std::cout << '\n';
    }
};

requires std::conjunction_v<std::is_same<Types, decltype(values)>...> is there to constrain the accepted non-type template parameters to what you supplied in Types.

Demo

Clang accepts

template <class... Types>
struct acceptor {
    template <Types... values>
    static void accept() { /* .. */ }
};

Demo but reject by gcc/msvc :-(

And all accept a "generic" version with requires:

template <class... Types>
struct acceptor {
    template <auto... values>
    requires((std::is_same_v<decltype(values), Types> && ...))
    static void accept() { /* .. */ }
};

Demo

In conjunction with the similar answers provided by @TedLyngmo (https://stackoverflow/a/79464046/28414083) and @Jarod42 (https://stackoverflow/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.

本文标签: Interpreting C variadic template arguments from other variadic template argumentsStack Overflow