Home > OS >  How to extract requires clause with a parameter pack whose parameters are related to each other into
How to extract requires clause with a parameter pack whose parameters are related to each other into

Time:11-20

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"; }

Demo.

  • Related