Search code examples
c++template-meta-programmingc++20c++-conceptstemplate-templates

partial specialised template as template argument


let's say for example I have a function foo() taking a parameter pack Ts. The function however should only accept the parameter pack Ts if there is exactly one type meeting the requirements of std::is_integral. The following code I've written does exactly that as expected.

#include <type_traits>

template <template <typename> typename predicate, typename... Ts>
inline constexpr std::size_t count_if = ((predicate<Ts>::value ? 1 : 0) + ...);

// this method only compiles if exactly one of the given template arguments is of an integral type
template <typename... Ts>
void foo() requires (count_if<std::is_integral, Ts...> == 1) {}

int main()
{
    foo<int, double, float>(); //this compiles
    //foo<int, long, float>(); //this doesn't, as it should
}

However, let's say the function foo() is being extended by a parameter of template type T like this:

#include <type_traits>

template <template <typename> typename predicate, typename... Ts>
inline constexpr std::size_t count_if = ((predicate<Ts>::value ? 1 : 0) + ...);

// this method only compiles if one of the given template arguments is of an integral type
template <typename T, typename... Ts>
void foo(T) requires (count_if<std::is_integral, Ts...> == 1) {}

int main()

{
    foo<double, int, double, float>(double{}); //this compiles
    foo<int, int, double, float>(int{}); //this compiles
    //foo<double, int, long, float>(double{}); //this doesn't, as it should
}

And now to my question: Is it possible, to make the predicate I'm giving to my count_if function dependent of the template parameter T of my function foo()?

For example, so I could check following:

#include <type_traits>

template <template <typename> typename predicate, typename... Ts>
inline constexpr std::size_t count_if = ((predicate<Ts>::value ? 1 : 0) + ...);

template <typename T, typename... Ts>
void foo(T) requires (count_if<std::is_same<T, ?????> , Ts...> == 1) {}

int main()
{
    foo<double, int, double, float>(double{}); //this should compile, as there is exactly one type in parameter pack of type double
    foo<int, int, double, float>(int{}); //this should compile, as there is exactly one type in parameter pack of type int
    //foo<int, double, float>(); //this shouldn't compile, as there is no int in parameter pack
    //foo<double, double, double>(); //this shouldn't compile, as there are two doubles in paramter pack
}

Is this somehow possible? I tried to check it using static_assert() inside the function body, but I can not define a template struct inside it, which I would need so I could write my own predicate like this for example:

template <typename T, typename... Ts>
void foo(T) 
{
  template <typename U>
  struct custom_predicate
  {
    static constexpr bool value = std::is_same<T, U>::value;
  };
    
  static_assert(count_if<custom_predicate, Ts...> == 1);
}

But like I said this isn't possible because I can't define template structs inside a function body.

Does someone have an idea?


Solution

  • It seems to me you're looking for

    template <template <typename, typename> typename predicate, typename T, typename... Ts>
    inline constexpr std::size_t count_if = ((predicate<T, Ts>::value ? 1 : 0) + ...);
    
    template <typename T, typename... Ts>
    void foo(T) requires (count_if<std::is_same, T, Ts...> == 1) {}
    

    But, this way, you have to change the count_if requirements.

    If you want (as you tried in the static_assert() example) a way to generate a specific is_same fixing the first type, you can write something similar your custom_predicate but outside the functions.

    For example

    template <template <typename> typename predicate, typename... Ts>
    inline constexpr std::size_t count_if = ((predicate<Ts>::value ? 1 : 0) + ...);
    
    template <typename T>
    struct my_predicate
     {
       template <typename U>
       using my_is_same = std::is_same<T, U>;
     };
    
    template <typename T, typename... Ts>
    void foo(T) requires (count_if<my_predicate<T>::template my_is_same, Ts...> == 1)
     { /* ... */ }
    

    Or maybe, to be more flexible (and passing the same std::is_same as parameter)

    template <template <typename> typename predicate, typename... Ts>
    inline constexpr std::size_t count_if = ((predicate<Ts>::value ? 1 : 0) + ...);
    
    template <template <typename...> class C, typename ... Ts>
    struct my_predicate
     {
       template <typename ... Us>
       using my_is_same = C<Ts..., Us...>;
     };
    
    template <typename T, typename... Ts>
    void foo(T)
       requires (count_if<my_predicate<std::is_same, T>::template my_is_same, Ts...> == 1)
     { }