Asked  7 Months ago    Answers:  5   Viewed   97 times

I am trying to get a simple example to work to understand how to use std::enable_if. After I read this answer, I thought it shouldn't be too hard to come up with a simple example. I want to use std::enable_if to choose between two member-functions and allow only one of them to be used.

Unfortunately, the following doesn't compile with gcc 4.7 and after hours and hours of trying I am asking you guys what my mistake is.

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc reports the following problems:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

Why doesn't g++ delete the wrong instantiation for the second member function? According to the standard, std::enable_if< bool, T = void >::type only exists when the boolean template parameter is true. But why doesn't g++ consider this as SFINAE? I think that the overloading error message comes from the problem that g++ doesn't delete the second member function and believes that this should be an overload.

 Answers

51

SFINAE only works if substitution in argument deduction of a template argument makes the construct ill-formed. There is no such substitution.

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

That's because when the class template is instantiated (which happens when you create an object of type Y<int> among other cases), it instantiates all its member declarations (not necessarily their definitions/bodies!). Among them are also its member templates. Note that T is known then, and !std::is_same< T, int >::value yields false. So it will create a class Y<int> which contains

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

The std::enable_if<false>::type accesses a non-existing type, so that declaration is ill-formed. And thus your program is invalid.

You need to make the member templates' enable_if depend on a parameter of the member template itself. Then the declarations are valid, because the whole type is still dependent. When you try to call one of them, argument deduction for their template arguments happen and SFINAE happens as expected. See this question and the corresponding answer on how to do that.

Tuesday, June 1, 2021
 
AlterPHP
answered 7 Months ago
38

More parentheses are required:

(bigCat.*pcat)();
^            ^

The function call (()) has higher precedence than the pointer-to-member binding operator (.*). The unary operators have higher precedence than the binary operators.

Sunday, June 6, 2021
 
Giovanni
answered 6 Months ago
69

I think according to the C++11 standard, this should be supported

Not really, because a non-static member function has an implicit first parameter of type (cv-qualified) YourType*, so in this case it does not match void(int). Hence the need for std::bind:

Register(std::bind(&Class::Function, PointerToSomeInstanceOfClass, _1));

For example

Class c;
using namespace std::placeholders; // for _1, _2 etc.
c.Register(std::bind(&Class::Function, &c, _1));

Edit You mention that this is to be called with the same Class instance. In that case, you can use a simple non-member function:

void foo(int n)
{
  theClassInstance.Function(n);
}

then

Class c;
c.Register(foo);
Thursday, June 24, 2021
 
Guesser
answered 6 Months ago
98

I think you need to expand the pack a in the capture list as well, like this:

template < typename ... A >
void f (A ... a) {
  g ([&, a...] () { h (a...); }); 
}

Here is the relevant text from the C++0x Final Committee Draft, section 5.1.2.23:

A capture followed by an ellipsis is a pack expansion (14.5.3). [ Example:

template<class... Args> void f(Args... args) {
    auto lm = [&, args...] { return g(args...); }; lm();
}

— end example ]

Friday, August 13, 2021
 
akosch
answered 4 Months ago
13

Nicol's solution works fine, but this is an alternative:

template<typename T>
struct Base
{
  void print1() {cout << "Base::print1" << endl;};
  void print2() {cout << "Base::print2" << endl;};
};

template<>
void Base<int>::print2() {cout << "Base<int>::print2()" << endl;};

That way you can specialize only specific member functions and still use those that you haven't specialized(in this case, print1) without any problem. So now you'd use it just like you wanted:

Base<int> i;
i.print1();
i.print2(); // calls your specialization

Demo here.

Sunday, September 5, 2021
 
cri_sys
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share