Using enable_if on virtual functions
Asked Answered
O

2

5
#include <type_traits>

class Base {
public:
    virtual bool f() {
        return true;
    }
};

template<typename T>
class Derived : public Base {
    std::enable_if_t< std::is_copy_constructible<T>::value, bool > f() override {
        return true;
    }

    std::enable_if_t< !std::is_copy_constructible<T>::value, bool > f() override {
        return false;
    }
};

The above code doesn't compile. For some reason I did not manage to understand, the compiler sees the two functions as the same overload before removing one by SFINAE.

What I do not understand, however, is how I am to resolve this problem. The docs I found state I should use templating on the function. That doesn't work, however, because the function is meant to be virtual.

I tried off-loading the problem by calling a non-virtual function, but I cannot get that to compile either:

template<typename T>
class Derived : public Base {
    virtual bool f() override {
        return f_impl();
    }

private:
    template< std::enable_if_t< std::is_copy_constructible<T>::value > = 0 >
    bool f_impl() {
        return true;
    }

    template< std::enable_if_t< !std::is_copy_constructible<T>::value > >
    bool f_impl() {
        return false;
    }
};

int main() {
    Derived<int> a;

    std::cout<<a.f()<<"\n";
}

That fails compilation with:

so.cpp: In instantiation of ‘class Derived<int>’:
so.cpp:29:18:   required from here
so.cpp:18:10: error: ‘std::enable_if<true, void>::type’ {aka ‘void’} is not a valid type for a template non-type parameter

I'm obviously doing something wrong here, but I can't figure out what would be the correct way.

Ojeda answered 4/8, 2019 at 9:31 Comment(2)
Why don't you just return a value bool f() override { return std::is_copy_constructible<T>::value; }?Orten
Because this is a simplified example. In actuality, I need to do two very different things.Ojeda
E
7

Unfortunately you can't do that. SFINAE works with templates; e.g. the following code revised from your 2nd sample works.

template< typename X = T>
std::enable_if_t< std::is_copy_constructible<X>::value, bool >
f_impl() {
    return true;
}

template< typename X = T>
std::enable_if_t< !std::is_copy_constructible<X>::value, bool >
f_impl() {
    return false;
}

LIVE

But virtual functions can't be template, that's all.

Extremism answered 4/8, 2019 at 9:40 Comment(2)
Can you elaborate on why this solution with std::is_copy_constructible<T> doesn't work?Ojeda
@ShacharShemesh std::enable_if should depend on the template parameter of the function template itself. Otherwise, SFINAE won't work; both the f_impl would be instantiated and either would be ill-formed certainly.Extremism
O
4

Using if constexpr it is possible to branch inside of function at compile time so function can stay virtual:

bool f() override
{
    if constexpr(std::is_copy_constructible<T>::value)
    {
        return true;
    }
    else
    {
        return false;
    }
}
Orten answered 4/8, 2019 at 9:41 Comment(15)
Can I do if constexpr(false) nonExstingFunction(); ? Because this is a simplified example.Ojeda
@ShacharShemesh It would be similar to the non-instantiated template - inactive branch won't be present in the body of the compiled function but syntax should be valid.Orten
Since the true branch of the if needs to actually call the copy constructor, I'm afraid this solution won't work for me. There I was, hoping C++ introduced static_if without me noticing....Ojeda
@ShacharShemesh it is indeed static_if and if does not work for you then variant with a template won't work eitherOrten
so if I got this correctly, the false side of the if constexpr needs to be syntactically correct, but it does not need to be semantically correct. It may reference non-existing functions and variables?Ojeda
that does not seem to be the case. int main() { if constexpr(false) nonexisting(); } does not compile (nonexisting not declared).Ojeda
@ShacharShemesh No, all the names used in the both branches must refer to some known entities. It is the same for templates. Temples don't allow referencing non-existing functions and variables even when they are not instantiated. Copy constructor of T type could be used in true branch if constexpr(std::is_copy_constructible<T>::value) without triggering an error when it is not actually available.Orten
So I can call a non-existing overload of an existing function, but not a function which is completely undeclared. That's very far from a true static_if, unfortunately.Ojeda
If you want the static_if from D, #ifdef is your friend (for this usage)Fir
Note if constexpr (false) doesn't work, however, it you can use another templated function that always returns false, it does work: if constexpr (Fake<T, false>::value)Fir
@Fir if constexpr (false) should work without problem, condition does not need to be dependent on template parameters.Orten
Is it? Cause it doesn't hold for the content (like static assert), I'll test itFir
@Fir argument of static_assert does not need to be dependent on template parameters either. But static_assert(false) simply makes program invalid.Orten
@Fir the use case here does not translate well to #ifdef at all. The condition is compile time known, not during preprocessing.Ojeda
In that case, the value ain't just false, it's dependent on T. In which case you are goodFir

© 2022 - 2024 — McMap. All rights reserved.