fedino
fedino

Reputation: 933

Why is SFINAE not applied in this example?

I expected the following minimal example to compile by application of SFINAE:

#include <iostream>
#include <string>
#include <type_traits>

struct FallBack{};

struct S {
    int bar() { return 42; }
};

template <typename A, typename B>
std::enable_if_t< (std::is_same< std::enable_if_t<true==FLAG, FallBack>, FallBack>::value and std::is_convertible<B, std::string>::value), int >
    foo ( A a) { return a.bar();
}


template <typename A, typename B>
std::enable_if_t<false==FLAG, int>
foo ( A a) { std::cout << "false solution " << std::endl; return -1; }



int main()
{
    std::cout << foo<S, std::string>( S{}) << std::endl;
}

to compile it: g++ -std=c++14 -DFLAG=1 myFile.cc

If I comment the second foo function, everything works fine, therefore my understanding of SFINAE is incorrect; furthermore, the compiler complains about an ambiguity on the definition of foo.

I am evidently wrong, but cannot see the problem. Could please anyone comment and explain why SFINAE is not applied?

Upvotes: 3

Views: 118

Answers (3)

skypjack
skypjack

Reputation: 50568

As already said by @Jarod42, sfinae expressions don't work with non template dependent conditions.

Anyway, if you can split it in two structs, here is a solution that does the job:

#include <iostream>
#include <string>
#include <type_traits>

struct S {
    int bar() { return 42; }
};

template <typename B, bool = FLAG>
struct T {
    template<typename A>
    static void foo ( A a ) {
        static_assert(std::is_convertible<B, std::string>::value, "!");
        std::cout << a.bar() << std::endl;
    }
};

template <typename B>
struct T<B, false> {
    template<typename A>
    static void foo( A a ) {
        static_assert(std::is_convertible<B, std::string>::value, "!");
        std::cout << "false solution " << std::endl;
    }
};

int main() {
    T<std::string>::foo(S{});
    T<std::string, true>::foo(S{});
    T<std::string, false>::foo(S{});
}

It is based on partial specialization instead of sfinae.
The drawback (or is it a feature?) of this solution is that you can still work around it and the value of FLAG by forcing the bool parameter explicitly.
See the code above for further details.

Upvotes: 1

Smeeheey
Smeeheey

Reputation: 10336

Why not just use:

#if FLAG == 1
template <typename A, typename B>
std::enable_if_t<std::is_convertible<B, std::string>::value, int >
    foo ( A a) { return a.bar();
}
#else
template <typename A, typename B>
int foo ( A a) { std::cout << "false solution " << std::endl; return -1; }
#endif

Given that you're using a compile-time define anyway, it makes sense to use it to explicitly specify the definitions you need rather than rely on SFINAE entirely.

Upvotes: 2

Jarod42
Jarod42

Reputation: 218323

You have non template dependent condition.

You should do something like

template <typename A, typename B>
std::enable_if_t<std::is_same<A, A>::value == FLAG, int>
foo (A a) { std::cout << "false solution " << std::endl; return -1; }

Upvotes: 4

Related Questions