Home > Net >  C force specific type for variadic template (even concepts seem not to work)
C force specific type for variadic template (even concepts seem not to work)

Time:12-21

If we have a constructor that accepts 3 parameters of type bool:

struct BoolOnly {
    BoolOnly (bool b1, bool b2, bool b3)
    {}
};

The compiler validates that we are actually calling it with boolean arguments:

BoolOnly b1 {0,1,1};    //ok
BoolOnly b2 {0,1,4};    //error: narrowing conversion of ‘4’ from ‘int’ to ‘bool’

It gives a compiler error, if a values is outside the range for boolean, which is perfectly OK.

Now the problem: I want to have a variadic template constructor, which I want to accept only arguments of type bool. In other words, I want to be able to pass an initializer consisting only of 0s or 1s. And get a compiler error if I pass something else

struct BoolOnlyExactVariadic {
    template<typename... Args>
    BoolOnlyExactVariadic (Args... args)
    {}
};

Here we cannot specify any type of the parameters. this constructor accepts any count of arguments of any types. There is still no support in C for homogeneous variadic template arguments.

The most advanced thing we have are concepts - they seem to be designed for setting restrictions on variadic template arguments. So, Let's try with std::same_as

template<typename T>
concept BooleanExact = std::same_as<T, bool>;

struct BoolOnlyExact {
    template<typename... Args>
    BoolOnlyExact (BooleanExact auto... b)
    {}
};

But the problem is that this template does not even accept values that are in the bool range:

BoolOnlyExact t2 {0,0,0}; //note: the expression ‘(... && BooleanExact<auto:12>) [with auto:12 = {int, int, int}]’ evaluated to ‘false’

The arguments get converted to int, which is not exact conversion to bool. Let's try then with std::convertible_to:

template<typename T>
concept BooleanConv = std::convertible_to<T, bool>;


struct BoolOnlyConv {
    template<typename... Args>
    BoolOnlyConv (BooleanConv auto... b)
    {}
};

Now the compilation succeeds, but there is no validation for argument values:

BoolOnlyConv t1 {4,4,5}; //ok, but we want to validate them

even if the values are outside the boolean range. (No idea why the compiler thinks that '5' is convertible to bool).

Is there any C 20/C 23 way to validate a variadic init list of bools at compile time, like the first example? Other than manually writing a function with 15 arguments? The problem here is that there may be cases with more arguments (for example, 9 or 25), for which separate functions will be needed?

CodePudding user response:

I would forget the idea of passing 0,1 as bools, use true,false, and check the type with std::same_as<bool>.

The only other options I see are:

  • If all your bools are constexpr, and so is the constructor body, you can make the constructor consteval and check the integers at compile-time to make sure they're in range.

  • Or perhaps create a template <bool...> struct BoolList {}; and pass that to the constructor. But that changes the call syntax.

CodePudding user response:

Not a great solution... but if you have an upper limit (99, in the following example) for the number of bools... you can recursively add the required only boolean constructors

We can start from a trivial struct that receive a number as template parameter, ignore it and set a using type as bool

template <std::size_t>
struct getBool
 { using type = bool; };

Then you can create a template recursive class AddC (for "add constructor") to add boolean only constructors

template <typename>
struct AddC;

template <std::size_t ... Is>
struct AddC<std::index_sequence<Is...>>
  : public AddC<std::make_index_sequence<sizeof...(Is)-1u>>
{
  using AddC<std::make_index_sequence<sizeof...(Is)-1u>>::AddC;

  AddC (typename getBool<Is>::type ... as)
  { };
};

// to stop the recursion
template <>
struct AddC<std::index_sequence<>>
{ };

Now BoolOnlyExactVariadic simply become

struct BoolOnlyExactVariadic
  : public AddC<std::make_index_sequence<100u>>
{ 
  using AddC<std::make_index_sequence<100u>>::AddC;
};

The following is a full compiling example (except for the t3 variable that gives a narrowing compilation error).

#include <utility>

template <std::size_t>
struct getBool
 { using type = bool; };

template <typename>
struct AddC;

template <std::size_t ... Is>
struct AddC<std::index_sequence<Is...>>
  : public AddC<std::make_index_sequence<sizeof...(Is)-1u>>
{
  using AddC<std::make_index_sequence<sizeof...(Is)-1u>>::AddC;

  AddC (typename getBool<Is>::type ... as)
  { };
};

// to stop the recursion
template <>
struct AddC<std::index_sequence<>>
{ };

struct BoolOnlyExactVariadic
  : public AddC<std::make_index_sequence<100u>>
{ 
  using AddC<std::make_index_sequence<100u>>::AddC;
};

int main()
{
  BoolOnlyExactVariadic t1 {0, 1, 0};             // compile
  BoolOnlyExactVariadic t2 {0, 0, 1, 1, 0, 0};    // compile
  BoolOnlyExactVariadic t3 {0, 0, 1, 1, 0, 0, 2}; // compilation error
}
  • Related