Question

I have been having some inexplicable SFINAE problems in a program I'm writing, so I boiled it down to a freestanding example program:

#include <type_traits>

struct Base { };

struct Derived : public Base { };

template<typename T>
struct From { };

template<typename T>
struct To {
    template<typename U>
    To(const From<U>& other) {
        static_assert(std::is_convertible<U*, T*>::value, "error");
    }
};

int main() {
    From<Derived> a;

    To<Base> b = a;
}

This program compiles without error or warning. However, this:

#include <type_traits>

struct Base { };

struct Derived : public Base { };

template<typename T>
struct From { };

template<typename T>
struct To {
    template<typename U>
    To(const From<typename std::enable_if<true, U>::type>& other) {
    // this       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        static_assert(std::is_convertible<U*, T*>::value, "error");
    }
};

int main() {
    From<Derived> a;

    To<Base> b = a;
}

Gives the following error:

test.cpp: In function int main():

test.cpp:22:18: error: conversion from From<Base> to non-scalar type To<Derived> requested

Which is because the substitution fails I presume, and the constructor isn't seen.

Am I doing SFINAE wrong, or is this a compiler bug? I am using rubenvb's GCC 4.7.1 on Windows (with std=c++11 if it makes a difference).

Was it helpful?

Solution

I'd use a default template argument, that way the argument can be deduced:

#include <type_traits>

struct Base { };

struct Derived : public Base { };

template<typename T>
struct From { };

template<typename T>
struct To {
    template<typename U, class = typename std::enable_if<true, U>::type>
    To(const From<U>& other) {
    // this       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        static_assert(std::is_convertible<U*, T*>::value, "error");
    }
};

int main() {
    From<Base> a;

    To<Derived> b = a;
}

Note that that causes the static_assert to fail since you are using std::is_convertible the other way around. It should be:

static_assert(std::is_convertible<T*, U*>::value, "error");

In your example, the template type U can't be deduced. In my code, it can be deduced, since it is being used as a template argument for the other argument in the constructor. In your code, the compiler sees a std::enable_if<true, U>::type and can't deduce what that U type is. The fact that the result of that enable_if is being used as a template argument for From doesn't help at all, since U needs to be deduced before that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top