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.