2

I have a non-movable structure and a templated class in which I want to have a function that exists only when the type is movable (using enable_if and type_traits). However, it seems that despite std::is_move_constructible_v returns false, the function still exists and can be executed. However, when I changed the code to use requires clause, everything works as intended. Why is that?

#include<bits/stdc++.h>

class NonMovable{
    public:
        NonMovable(const NonMovable&) = default;
        NonMovable(NonMovable&&) = delete;
        
        NonMovable& operator =(const NonMovable&) = default;
        NonMovable& operator = (NonMovable&&) = delete;
        
        NonMovable() = default;
};

template<typename T>
struct Foo{
    template<typename = std::enable_if<std::is_move_constructible_v<T>,bool>>
    void foo(T&& t){ // allowed
        // ...
    }
};

template<typename T>
struct Foo{
    void foo(T&& t) requires std::is_move_constructible_v<T>{ // not allowed
        // ...
    }
};

int main(){
    NonMovable nonMovable;
    Foo<NonMovable> x;
    std::cout << std::is_move_constructible_v<NonMovable> << "\n"; // 0
    x.foo(std::move(nonMovable));
}
capi1500
  • 115
  • 1
  • 6
  • 4
    Looks like you meant `enable_if_t`, not `enable_if`. – Nathan Pierson Jun 07 '21 at 20:32
  • Do you only want to disable that constructor, not the whole class instance? – Ted Lyngmo Jun 07 '21 at 20:32
  • @NathanPierson thank you, that was the case – capi1500 Jun 07 '21 at 20:53
  • @TedLyngmo intention was to disable just the function. I managed to do this with `requires` clause. 'enable_if_t` disables the whole class. – capi1500 Jun 07 '21 at 20:55
  • And so it is _t or ::type missing ... so typo level problem. – Öö Tiib Jun 07 '21 at 20:55
  • 1
    For using SFINAE to disable _just_ the member function and not the entire class, take a look at [this question](https://stackoverflow.com/questions/30953248/why-doesnt-sfinae-enable-if-work-for-member-functions-of-a-class-template). – Nathan Pierson Jun 07 '21 at 20:58
  • @capi1500 I just wanted to make sure (and I thought `foo` was `Foo`s constructor - I didn't see that it was a regular member function until now). I added an answer to what I think you want. – Ted Lyngmo Jun 07 '21 at 21:08

2 Answers2

4

If you only want to disable that foo function for non move constructible types, you could make the template parameter a dependent type:

template<typename T>
struct Foo{
    template<class U = T, class = std::enable_if_t<
                                      std::is_same_v<T,U>&&
                                      std::is_move_constructible_v<U>>>
    void foo(T&&){ // not allowed
        // ...
    }
};

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 1
    I think you might need to do `std::is_move_constructible_v>` or include `std::is_same_v` in the `enable_if` or something to disable things like allowing the creation of `foo` for `U = NonMovable&`. Concepts and requirements really are an advance... – Nathan Pierson Jun 07 '21 at 21:11
  • @NathanPierson Good point. `is_same` added – Ted Lyngmo Jun 07 '21 at 21:14
  • Actually the issue that came up for me in my messing around was when I had made the signature `void foo(U&&)` instead of `void foo(T&&)`, which leaves more room for the compiler to deduce the value of `U`. Still, since the intent seems to be that the member function shouldn't actually be templated separately from the enclosing struct, it's worth locking down weird instantiations of it. – Nathan Pierson Jun 07 '21 at 21:17
  • @NathanPierson I actually don't know if having `U` or `T` in `foo(x&&)` makes a difference (after I added your idea with `std::is_same`).. Is there some corner case that makes `U` preferable here? – Ted Lyngmo Jun 07 '21 at 21:25
  • 1
    It definitely makes a difference if you don't have `is_same`, but I don't know which is preferable. Indeed I think using `T` as you did actually is preferable. The test calling code I was looking at was `x.foo(nonMovable);` instead of `x.foo(std::move(nonMovable));`. Using `U` instead of `T` meant that it actually successfully instantiated because it `U&&` in that context is a forwarding reference, to an lvalue `NonMovable&`, which is apparently move-constructible. Using `T&&` instead of `U&&` seems to shut that down. I think after the `is_same` it doesn't make a difference. – Nathan Pierson Jun 07 '21 at 21:29
  • @NathanPierson Oh, right ... the forwarding reference in this context totally slipped my mind. Many thanks! It's not that it becomes move-constructible in that context but it could be forwarded to a copy constructor I guess – Ted Lyngmo Jun 07 '21 at 21:33
0

As @Nathan Pierson mentioned in the comment, there should be enable_if_t instead of enable_if

capi1500
  • 115
  • 1
  • 6