I've got such a set of toy functions:
template <typename... Args>
requires std::conjunction_v<std::is_convertible<Args, int>...>
void test(Args...) { std::cout << "int"; }
template <typename... Args>
requires std::conjunction_v<
std::disjunction<std::is_convertible<Args, int>,
std::is_convertible<Args, std::string>>...> &&
std::disjunction_v<std::is_convertible<Args, std::string>...>
void test(Args...) { std::cout << "istring"; }
The first function will be called when I uses arguments that are all convertible to int and unconvertible to std::string, like: test(1, 2L, 3.0, 4UL)
.
If there's at least one argument that is convertible to std::string and if all arguments are either convertible to int or std::string, the second function will be called, like test(1, 2L, "Hello", 4UL)
.
And it does as I expected.
However, when I code the second function as the below two style, it doesn't work. The concept of arguments are checked one after another, and 1, 2L, 4UL
are not a Istring
.
template <typename... Args>
concept Istring = std::conjunction_v<
std::disjunction<std::is_convertible<Args, int>,
std::is_convertible<Args, std::string>>...> &&
std::disjunction_v<std::is_convertible<Args, std::string>...>;
void test(Istring auto...) { std::cout << "istring"; }
template <typename... Args>
concept Istring = std::conjunction_v<
std::disjunction<std::is_convertible<Args, int>,
std::is_convertible<Args, std::string>>...> &&
std::disjunction_v<std::is_convertible<Args, std::string>...>;
template <IString... Args>
void test(Args...) { std::cout << "istring"; }
I'm wondering whether there's a way to extract the concept.
CodePudding user response:
There is no need to use std::conjunction
and std::disjunction
in such a case since it makes the code verbose and difficult to read. Using fold expressions will be more intuitive.
template <typename... Args>
requires (std::is_convertible_v<Args, int> && ...)
void test(Args...) { std::cout << "int\n"; }
template <typename... Args>
concept Istring =
((std::is_convertible_v<Args, int> ||
std::is_convertible_v<Args, std::string>) && ...) &&
(std::is_convertible_v<Args, std::string> || ... );
template <Istring... Args>
void test(Args...) { std::cout << "istring\n"; }
In the above example, Istring
will only constrain a single parameter instead of the parameter pack, that is, check whether each parameter satisfies the following degenerate constraint:
template <typename T>
concept Istring =
(std::is_convertible_v<T, int> || std::is_convertible_v<T, std::string>) &&
std::is_convertible_v<T, std::string>;
You should use the requires
clause to check all parameters, for example:
template <typename... Args>
requires Istring<Args...>
void test(Args...) { std::cout << "istring\n"; }