Search code examples
c++c++20variadic-templatesvariadic-functionsc++-concepts

How do I use concepts to constrain the argument types for variadic functions?


I have a variadic function that can take any combination of input arguments, as long as each one of those arguments is convertible to bool:

#include <concepts>
#include <cstddef>

// internal helper functions
namespace {
    template <typename T>
    constexpr std::size_t count_truths(T t) {
        return (bool)t;
    }

    template <typename T, typename... Args>
    constexpr std::size_t count_truths(T t, Args... args) { // recursive variadic function
        return count_truths(t) + count_truths(args...);
    }
}

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

// variadic function for which all arguments should be constrained to Booly<T>
// e.g. only_one(true, false, true, false, true) = false; only_one(true, false) = true
template <typename T, typename... Args> requires Booly<T>
constexpr bool only_one(T t, Args... args) {
    return count_truths(t, args...) == 1;
}

I have attempted to constrain the templates using concepts to only allow bool-convertible types to be passed, but I have only managed to do so for the first parameter:

// following lines compile:
only_one(true, false, false);
only_one(BoolConvertible(), true, false); 

// this line is correctly forced to failure due to the concept not being satisfied:
only_one(NonBoolConvertible(), false, true);

// BUT this line is not detected as a concept constraint failure (but still compilation failure):
only_one(true, NonBoolConvertible(), false, true);

How can I use C++20 concepts to constrain the remaining template parameters to ensure each one of them in Args... satisfies Booly<> ?


Solution

  • You can simply use C++17 fold expression to do this

    #include <concepts>
    
    template<std::convertible_to<bool>... Args>
    constexpr bool only_one(Args... args) {
      return (bool(args) + ... + false) == 1;
    }
    
    static_assert(only_one(true, false, true, false, true) == false);
    static_assert(only_one(true, false) == true);
    static_assert(only_one() == false); // allow empty pack