2

This is kind of a followup question to my previous one (Unified way for checking the existence of member functions, free functions and operators).

I am trying to generate operators for scalar * vector and vector * scalar only if they are not provided by the vector class at hand. I check if those operators exist in the following way:

template<typename C, typename Ret, typename Arg>
struct has_operator_mult {
private:
    template<typename L, typename R, std::enable_if_t<
        std::is_convertible
        <
            decltype(std::declval<L>() * std::declval<R>()),
            Ret
        >::value
    > * = nullptr >
        static constexpr std::true_type check(nullptr_t);

    template<typename, typename>
    static constexpr std::false_type check(...);

public:
    typedef decltype(check<C, Arg>(nullptr)) type;
    static constexpr bool value = type::value;
};

For vectors from the Eigen library (version 3.3.4) this produces:

has_operator_mult<Vector3f, Vector3f, float>::value; // true -> Vector3f = Vector3f * float
has_operator_mult<float, Vector3f, Vector3f>::value; // true -> Vector3f = float * Vector3f

has_operator_mult<Vector3f, Vector3f, std::vector<float>>::value // false

which is fine. However, as soon as I try to declare the righthandside multiplication operator:

template<typename TVector3D,
    std::enable_if_t
    <
        !has_operator_mult<TVector3D, TVector3D, float>::value
    > * = nullptr
>
TVector3D operator*(const TVector3D & lhs, float rhs)
{
    return TVector3D{}; // implementation does not matter here
}

the output changes to

has_operator_mult<Vector3f, Vector3f, float>::value; // true  -> Vector3f = Vector3f * float
has_operator_mult<float, Vector3f, Vector3f>::value; // false -> Vector3f = float * Vector3f

So the operator that I am not declaring myself somhow vanishes according to my existence check. Interestingly this does not change whether I use

std::enable_if_t
<
    !has_operator_mult<TVector3D, TVector3D, float>::value // with negation
>

or

std::enable_if_t
<
    has_operator_mult<TVector3D, TVector3D, float>::value // without negation
>

Both operators however are still callable, but call the implementations of the Eigen library.

Vector3f v(1, 2, 3);
std::cout << v * 5.f << std::endl << std::endl;
std::cout << 5.f * v << std::endl << std::endl;

When I add the other multiplication operator, things get even more confusing:

template<typename TVector3D,
    std::enable_if_t
    <
        !has_operator_mult<float, TVector3D, TVector3D>::value
    > * = nullptr
>
TVector3D operator*(float lhs, const TVector3D & rhs)
{
    return TVector3D{};
}

has_operator_mult<Vector3f, Vector3f, float>::value; // false -> Vector3f = Vector3f * float
has_operator_mult<float, Vector3f, Vector3f>::value; // true  -> Vector3f = float * Vector3f

Vector3f v(1, 2, 3);
std::cout << v * 5.f << std::endl << std::endl; // calls my implementation
std::cout << 5.f * v << std::endl << std::endl; // calls Eigen implementation

std::cout << v * 5 << std::endl << std::endl; // compiler error, ambiguous function call
std::cout << 5 * v << std::endl << std::endl; // calls Eigen implementation

Why is this happening? Is the check for the existence of the operator wrong? It seems to work as long as I am not declaring operators myself.

Devon Cornwall
  • 957
  • 1
  • 7
  • 17
  • I think you are in UB land (or near), as `has_operator_mult::value` would have different value, before and after your overload. – Jarod42 Apr 17 '18 at 19:09
  • That would be the case if TVector3D is a type that does not provide such an operator. In such a situation I would want `has_operator_mult` inside of `enable_if` to be evaluated to false so that the template is instanciated for this type. In the code above Eigen::Vector3f already has the desired operator which somehow seems to not be recognized by MSVC -- at least when put inside the template parameter. GCC seems to handle this just fine. – Devon Cornwall Apr 18 '18 at 07:43

1 Answers1

2

With gcc and clang your code snippets work fine, but not with MSVC. So I recommend you to report the issue to the MSVC team.

You can try by yourself with the three compilers there.

ggael
  • 28,425
  • 2
  • 65
  • 71
  • Thank you. I should have mentioned the compiler I am working on. I filed a report here: https://developercommunity.visualstudio.com/content/problem/235349/conditionally-compiling-template-operator-overload.html – Devon Cornwall Apr 17 '18 at 15:08
  • Work around: using `typename std::enable_if::type` instead of `std::enable_if_t` fixes SFINAE on MSVC (at least in some cases). – Jarod42 Apr 18 '18 at 08:39