C++ function call is ambiguous with concepts

94 views Asked by At

I have a C++ macro that I want to use to call a function template which is overloaded. I want one of these function templates to only accept floating point values and the other function template will accept everything else.

Here is the code that I run in https://cpp.sh/ with C++ 20:

#include <iostream>
#include <string>
#include <vector>
#include <type_traits>
#include <concepts>

template< typename T>
concept NotFloatingPoint = requires
{
    !std::is_same_v<T, double> || !std::is_same_v<T, float>;
};

template< NotFloatingPoint T, NotFloatingPoint U, bool isDistanceType_ = false >
void ProcConstantConfigForce( T& value_ )
{
    std::cout << "Inside ProcConstantConfigForce()\n";
    std::cout << value_;
}

template< typename T>
concept FloatingPoint = requires
{
    std::is_same_v<T, double> || std::is_same_v<T, float>;
};

template< FloatingPoint T, FloatingPoint U, bool isDistanceType_ = false >
void ProcConstantConfigForce( T& value_ )
{
    std::cout << "Inside ProcConstantConfigForce() for doubles\n";
    std::cout << value_;

    if ( isDistanceType_ )
    {
        value_ *= 1.5;
    }
}

#define FORCE_CONSTANT( constant_, isDistanceType_ ) ProcConstantConfigForce< decltype( constant_ ), decltype( constant_ ), isDistanceType_ >

int main()
{
  double val = 1.0;
  FORCE_CONSTANT(1.0, true)(val);
  std::cout << "Updated val: " << val;
}

However, the compiler complains about it being ambiguous:

main.cpp:43:3: error: call to 'ProcConstantConfigForce' is ambiguous
  FORCE_CONSTANT(1.0, true)(val);
  ^~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:38:54: note: expanded from macro 'FORCE_CONSTANT'
#define FORCE_CONSTANT( constant_, isDistanceType_ ) ProcConstantConfigForce< decltype( constant_ ), decltype( constant_ ), isDistanceType_ >
                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:14:6: note: candidate function [with T = double, U = double, isDistanceType_ = true]
void ProcConstantConfigForce( T& value_ )
     ^
main.cpp:27:6: note: candidate function [with T = double, U = double, isDistanceType_ = true]
void ProcConstantConfigForce( T& value_ )
     ^
1 error generated.

I have found that I can solve my problem using constexpr with the std::floating_point concept and just use a single function template. However, I specifically want to know why the compiler complains that it is ambiguous since it is clear that only one function should be used for floating point values so why does it try to instantiate a template of the first function when the concept should restrict it?

thank you for the help.

1

There are 1 answers

0
Brian Bi On

There are two problems with your code. First,

template< typename T>
concept NotFloatingPoint = requires
{
    !std::is_same_v<T, double> || !std::is_same_v<T, float>;
};

doesn't do what you think it does: requires { expr; } checks whether expr is a valid expression, not whether expr is true. So you should rewrite this as

template< typename T>
concept NotFloatingPoint = !std::is_same_v<T, double> || !std::is_same_v<T, float>;

But it's still incorrect. It now checks whether one of two things is true: T is not double, or T is not float. That means that even if T is double, it still satisfies NotFloatingPoint because double is not float. It's impossible for NotFloatingPoint to ever be false. The correct definition is:

template< typename T>
concept NotFloatingPoint = !std::is_same_v<T, double> && !std::is_same_v<T, float>;

Or an easier way might be:

template< typename T>
concept FloatingPoint = std::is_same_v<T, double> || std::is_same_v<T, float>;

template <typename T>
concept NotFloatingPoint = !FloatingPoint<T>;