4

For example: define a C++ concept with the following constraint: the type should have a function template called f that receive an integer value as the parameter.

Here is a valid type:

struct A
{
    template <std::integral T>
    void f();
};

when defining the corresponding concept, I can write:

template <typename T>
concept HasValidF = requires(T t) {
    t.f<int>(0);
};

But this one cannot cover all other integral types. A modification might be

template <typename T>
concept HasValidF = requires(T t) {
    t.f<int>(0);
    t.f<long>(0);
    t.f<unsigned>(0);
    ...
};

But the above implementation is not ideal. Are there anyway to avoid such enumeration? Thanks!

4
  • Note that struct B { template <typename T> void f(); }; would be accepted with your definition of HasValidF, which might not be what you want. Commented Jun 28 at 21:48
  • Can you give an example of how you would use HasValidF? I don't get it. Commented Jun 28 at 22:57
  • Your HasValidF will fail since f doesn't take any arguments. Commented Jun 29 at 1:58
  • template <std::integral T> void f(); should be template <std::integral T> void f(T) Commented Jun 29 at 3:57

2 Answers 2

5

I'd create a helper. One of these may suite you:

template <class T, class... Ts>
concept HasOverloadForOneOf =
    (... || requires { std::declval<T>().template f<Ts>(); });

template <class T, class... Ts>
concept HasOverloadForAllOf =
    (... && requires { std::declval<T>().template f<Ts>(); });

Then defining HasValidF becomes a little simpler although you still have to list all the types that should be taken into consideration. If you want your type to have at least one matching overload, you'd use HasOverloadForOneOf. If you want it to have overloads for all, use HasOverloadForAllOf:

template <class T>
concept HasValidF =
    HasOverloadForAllOf<T, bool, char, signed char, unsigned char, short,
                        unsigned short, int, unsigned, long, unsigned long,
                        long long, unsigned long long>;

Demo


If you want all const, volatile and const volatile versions too, you could either list them manually in HasOverloadForOneOf/HasOverloadForAllOf or add helpers to create them:

template <class...> struct make_const;
template <class... Ts>
struct make_const<std::tuple<Ts...>> {
    using type = std::tuple<std::add_const_t<Ts>...>;
};
template <class T> using make_const_t = make_const<T>::type;

template <class...> struct make_volatile;
template <class... Ts>
struct make_volatile<std::tuple<Ts...>> {
    using type = std::tuple<std::add_volatile_t<Ts>...>;
};
template <class T> using make_volatile_t = make_volatile<T>::type;
//------------------------------------------------------------------------------
using base_integrals = std::tuple<bool, char, signed char, unsigned char, short,
                                  unsigned short, int, unsigned, long,
                                  unsigned long, long long, unsigned long long>;
using all_integrals =
    decltype(std::tuple_cat(base_integrals{},
                            make_const_t<base_integrals>{},
                            make_volatile_t<base_integrals>{},
                            make_const_t<make_volatile_t<base_integrals>>{}));
//------------------------------------------------------------------------------
template<class...> struct HasValidFHelper;
template<class T, class... Ts> struct HasValidFHelper<T, std::tuple<Ts...>> {
    static constexpr bool value = HasOverloadForAllOf<T, Ts...>;
};
template <class T>
concept HasValidF = HasValidFHelper<T, all_integrals>::value;

Demo

Sign up to request clarification or add additional context in comments.

Comments

0

Note: this will not directly answer the question but aims to show that it might be difficult to define such a concept.

First, a concept that rejects classes that have no f function can be easily detected. For instance,

struct C {
    template <typename T>
    void g();
};

wouldn't fit the concept

template <typename T>
concept HasValidF = requires(T t) {
    t.template f<int>();
};

On the other hand, let's consider the following class:

struct B {
    template <typename T>
    void f();
};

You can check that it respects the concept HasValidF since it can be used with a f template function member taking an int as template argument, and maybe it is not what you intented. Here, B is more generic than A but the concept will work for both.

Actually, you would need the opposite concept that tells which types cannot be used for the instanciation (e.g. float), something like

template <typename T>
concept HasInvalidF = requires(T t) {
    t.template f<float>();
};

and indeed the class A doesn't fit this concept but it makes your problem even harder.

1 Comment

Note that HasInvalidF says nothing about if there is an overload accepting integral parameters and it would also not work if there are overloads for both integral and floating point types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.