Search code examples
c++variadic-templatesc++-concepts

C++ force specific type for variadic template (even concepts seem not to work)


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?


Solution

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