Newline
Newline

Reputation: 941

Overloading functions with concepts

(I'm learning concepts and templates so correct me if I'm very wrong with something.) I have a function that takes a concept as parameter. I'm now trying to overload this function that takes a more specific concept. That would do "something more specific" or call the less specific function.

template<typename T>
concept Concept1 = ...;

template<typename T>
concept MoreSpecificConcept = ...;
    Concept1 <T> &&
    ...;

//...
void someFunc(const Concept1 auto& x)
{
  //do general stuff
}

void someFunc(const MoreSpecificConcept auto& x)
{
  if(...)
  {
    //do specific stuff
  }
  else
  {
    //do the more general thing:

    // Problem. Trying to call itself:
    someFunc(x);
  }
}

Is there any way to explicitly tell the compiler which overload to call (like someFunc<Concept1>(x) which doesn't work), or is it solely dependent on the type of the passed object? Lets say that I can't cast x to the more-general type and that the more general function / concept don't know about this more specific function / concept so they can't exclude it with a constraint. Edit: the functions are meant to be within the same (global) namespace.

Upvotes: 8

Views: 936

Answers (3)

Marek R
Marek R

Reputation: 37697

Here is my version of Minimal Complete Verifiable Example

#include <iostream>
#include <type_traits>

template <typename T>
concept aritmetic = std::is_arithmetic_v<T>;

template <typename T>
concept real = std::is_floating_point_v<T>;

template<aritmetic T>
void foo(T x)
{
    std::cout << __PRETTY_FUNCTION__ << " x + 1 = " << x + 1 << '\n';
}

#ifdef HAS_OVERLOAD
void foo(real auto x)
{
    std::cout << __PRETTY_FUNCTION__ << " x / 2 = " << x / 2 << '\n';
}
#endif

int main()
{
    foo(1);
    foo(1.1);

    return 0;
}

And here is my idea how to fix it:

#include <iostream>
#include <type_traits>

template <typename T>
concept aritmetic = std::is_arithmetic_v<T>;

template <typename T>
concept real = std::is_floating_point_v<T>;

template<aritmetic T>
void foo(T x) requires (!real<T>)
{
    std::cout << __PRETTY_FUNCTION__ << " x + 1 = " << x + 1 << '\n';
}

void foo(real auto x)
{
    std::cout << __PRETTY_FUNCTION__ << " x / 2 = " << x / 2 << '\n';
}

int main()
{
    foo(1);
    foo(1.1);

    return 0;
}

I think this version is nice and clean: no extra function is needed or this nasty if.

Upvotes: 0

Jarod42
Jarod42

Reputation: 217255

If possible (mostly depends of both concepts), Another work-around is to create wrapper which only satisfies Concept1 but not MoreSpecificConcept:

template <Concept1 T>
struct AsConcept1
{
    T& t;

    // operations to satisfy Concept1 (but not MoreSpecificConcept)
    // using some_type = typename Concept1::some_type
    // void bar() { t.bar(); }
    // ...
};

and then you might do

void someFunc(const MoreSpecificConcept auto& x)
{
    if (...) {
        // do specific stuff
    } else {
        // do the more general thing:
        someFunc(AsConcept1{x});
    }
}

Upvotes: 1

Guillaume Racicot
Guillaume Racicot

Reputation: 41770

The usual workaround for that would be a separated helper function:

void somefunc(const Concept1 auto& x) {
  // general stuff
}

void somefuncSpecific(const Concept1 auto& x) {
  somefunc(x);
}

void someFuncSpecific(const MoreSpecificConcept auto& x)
{
  if(...)
  {
    //do specific stuff
  }
  else
  {
    //do the more general thing:
    somefunc(x);
  }
}

Another workaround without having separated functions is to use if constexpr:

void someFuncSpecific(const Concept1 auto& x)
{
  if constexpr(MoreSpecificConcept<decltype(x)>)
  {
    if (...)
    {
      //do specific stuff

      // skip the rest:
      return;
    }
  }
  //do the more general thing:
  somefunc(x);
}

Upvotes: 7

Related Questions