Joe
Joe

Reputation: 6806

SFINAE fails for custom template. Need to understand why

My code needs to test various pixel types for "validity". For example, floating point pixels are invalid if they report true for std::isnan().

So I have a "validator" template struct that I specialize for my various pixel types (here, just for float). My code uses a global template function to invoke the right overload through SFINAE

// Dummy implementation breaks compilation if no overload found.
template<class PEL, typename Enable=void> struct pixel_validator  { };


template<class PEL> 
struct pixel_validator<PEL, typename std::enable_if<std::is_floating_point<PEL>::value>::type>
{
    static bool validate(const PEL& p) { return !std::isnan(p);  }
};


template<class PEL>
inline bool is_valid_pixel(const PEL& p) 
{
    // Dispatch to validator above
    return pixel_validator<PEL>::validate(p); 
};


void main
{
     float x = 1.0f;
     std::cout << "is it valid ?" << std::boolalpha << is_valid_pixel(x);
}

And this example works just fine. The pixel_validator specialization for float is chosen. All is well.

But then I tried to reduce the verbosity of my template expressions for clarity via a custom version of "std::enable_if" specifically for float.

template<class T, class VAL=T>
struct enable_if_floating
    : std::enable_if<std::is_floating_point<T>::value, VAL>
{};

So now instead of writing this:

std::enable_if<std::is_floating_point<PEL>::value>::type

I can write

enable_if_floating<PEL>::value

... so my validator becomes:

template<class PEL> 
struct pixel_validator<PEL, typename enable_if_floating<PEL>::type>
{
    static bool validate(const PEL& p) { return !std::isnan(p); }
};

Unfortunately, the moment that I change my "pixel_validator" to use it, the code fails to build. My enable_if_floating does not work and so Visual Studio cannot find the appropriate specialization. My output is not surprising then.

1>------ Build started: Project: TestApp7, Configuration: Debug Win32 ------
1>TestApp7.cpp
1>C:\Test\TestApp7\TestApp7.cpp(62,34): error C2039:  'validate': is not a member of 'pixel_validator<PEL,void>'
1>C:\Test\TestApp7\TestApp7.cpp(62,34): error C2039:         with
1>C:\Test\TestApp7\TestApp7.cpp(62,34): error C2039:         [
1>C:\Test\TestApp7\TestApp7.cpp(62,34): error C2039:             PEL=float
1>C:\Test\TestApp7\TestApp7.cpp(62,34): error C2039:         ]
1>C:\Test\TestApp7\TestApp7.cpp(62): message :  see declaration of 'pixel_validator<PEL,void>'
1>C:\Test\TestApp7\TestApp7.cpp(62): message :         with
1>C:\Test\TestApp7\TestApp7.cpp(62): message :         [
1>C:\Test\TestApp7\TestApp7.cpp(62): message :             PEL=float
1>C:\Test\TestApp7\TestApp7.cpp(62): message :         ]
1>C:\Test\TestApp7\TestApp7.cpp(82): message :  see reference to function template instantiation 'bool is_valid_pixel<float>(const PEL &)' being compiled
1>C:\Test\TestApp7\TestApp7.cpp(82): message :         with
1>C:\Test\TestApp7\TestApp7.cpp(82): message :         [
1>C:\Test\TestApp7\TestApp7.cpp(82): message :             PEL=float
1>C:\Test\TestApp7\TestApp7.cpp(82): message :         ]
1>C:\Test\TestApp7\TestApp7.cpp(62,1): error C3861:  'validate': identifier not found
1>Done building project "TestApp7.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

My question is, why? What is wrong with my enable_if_floating?

Note: I even put this code in my main(), just for sanity checking. If my template were bad, I would expect the static_assert() to fail, but it does not.

// Sanity check #2.  Does my enable_if_floating test  reports that float
// enables because it's numeric?  If not then the static_assert below should fail

using float_type = enable_if_floating<float>::type;
static_assert(std::is_same_v<float_type, float>, "Not same as float...");

Note also: My real world code uses a predicate that saves a whole lot more space than in this simple example

Upvotes: 1

Views: 180

Answers (1)

max66
max66

Reputation: 66210

Not sure is the only problem but is a problem.

If you write

template<class T, class VAL=T>
struct enable_if_floating
    : std::enable_if<std::is_floating_point<T>::value, VAL>
{};

the default returned type is T where, for std::is_enable_if, is void.

So

template<class PEL> 
struct pixel_validator<PEL, typename enable_if_floating<PEL>::type>
{
    static bool validate(const PEL& p) { return !std::isnan(p); }
};

become, when PEL is a floating point type,

template<class PEL> // .....VVV   should be void, not PEL
struct pixel_validator<PEL, PEL>
{
    static bool validate(const PEL& p) { return !std::isnan(p); }
};

that doesn't matches the pixel_validator declaration (and main definition)

template<class PEL, typename Enable=void>
struct pixel_validator
 { };

because the expected second type is void, not PEL.

I see two possible alternative solutions: or you use void as default value for enable_is_floating second template parameter

// ...........................VVVV 
template<class T, class VAL = void>
struct enable_if_floating
    : std::enable_if<std::is_floating_point<T>::value, VAL>
 { };

or you use PEL as default value for pixel_validator second template parameter

template <typename PEL, typename = PEL>
struct pixel_validator
 { };

I suggest the first one to homogeneity with std::enable_if and standard C++ library.

Upvotes: 2

Related Questions