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

Time:10-22

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<> ?

CodePudding user response:

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

CodePudding user response:

You can expand Args via (Booly<Args> && ...) to pass each indivual type onto Booly. Chaining the result with && will therefore only yield true if all types satisfy Booly.

template <typename T, typename... Args> requires Booly<T> && (Booly<Args> && ...)
constexpr bool only_one(T t, Args... args) {
    return count_truths(t, args...) == 1;
}

Demo:

struct foo {
    operator bool();
};
struct bar {};

int main() {
    only_one(true, true, false);
    only_one(foo{}, true);
    only_one(bar{}, true); //C2672 
    only_one(true, bar{}); //C2672 
}

CodePudding user response:

After following up on a related question after drafting my own, I was inspired by the following answer (https://stackoverflow.com/a/64694337/6177253) to discover an easy solution.

Rather than using requires to constrain on our Booly concept, just use the concept directly when constraining the template parameters:

template <Booly T, Booly... Args>
constexpr bool only_one(T t, Args... args) {
    return count_truths(t, args...) == 1;
}

This modification now enforces the constraint on all parameters, at least as far as my own tests can tell.

I leave my question open and this answer here, in case anyone discovers any issues with my approach or potential improvements.

  • Related