Search code examples
c++templatesgccc++14gcc5.2

template being deduced as wrong type with template variable


So I have the following test code:

struct empty_value{
    template<typename T>
    T as(){ return T(0); }
};

template<typename T, typename U, typename F>
auto empty_func(empty_value lhs, empty_value rhs, F f) -> decltype(f(lhs.as<T>(), rhs.as<U>())){
    return f(lhs.as<T>(), rhs.as<U>());
}

template<typename T, typename U, template<typename, typename> class F>
static auto bind_empty_f = std::bind(empty_func<T, U, F<T, U>>, std::placeholders::_1, std::placeholders::_2, F<T, U>{});

template<typename F>
void other_test_func(F&&){}

template<typename T, typename U, template<typename, typename> class F>
void test_func(){
    other_test_func(bind_empty_f<T, U, F>);
}

template<typename T, typename U>
struct my_add{
    decltype(auto) operator()(T lhs, U rhs){ return lhs + rhs; }
};

int main(){
    test_func<float, int, my_add>();
}

Which is derived from something I was actually working on. The problem arises on the line of bind_empty_f. But only when it is passed to other_test_func. When I try to assign it to a regular variable like this:

int main(){
    auto var = bind_empty_f<float, int, my_add>;
}

Everything is all cheery. But if I call test_func which tries to pass it to other_test_func I get an error that the underlying type returned by std::bind can't be converted to float. So it is trying to convert it to the return value of the actual function. I can't understand why. Where am I passing the function's return value?


EDIT

if I call the function after setting a local variable to the value of bind_empty_f first it compiles:

int main(){
    auto var = bind_empty_f<float, int, my_add>;
    test_func<float, int, my_add>;
}

So the issue must be to do with static initialization a compiler bug.

EDIT2

As stated in the comments, this exact example compiles with other compilers but does not with the original tested compiler (GCC 5.2.0).

This is a bug in either GCC 5.2 or every other compiler tested.

So I guess the question becomes, is this standard conforming code?


Solution

  • Here is a minimal example of your problem:

    template<class T> struct tag {};
    
    template<typename T>
    static auto bind_empty_f = tag<T>{};
    
    template<typename T>
    decltype(bind_empty_f<T>) test_func(){
      return 3.14f;
    }
    

    then we simply test_func<float>() and it returns 3.14f. If we test_func<int>() it returns 3.

    If we first do a bind_empty_f<float>, instead test_func<float> generates an error.

    The type deduced for bind_empty_f<T> when it is called within another template is set to T and not to the type of the right hand side of the expression.

    If you call it directly, and the type hasn't already been calculated (there seems to be a cache), then the correct type is deduced, and my test_func fails to build (as it tries to convert 3.14f to a bind expression type and fails).

    This is definitely a problem with the compiler. You can work around it by replacing the auto in bind_empty_f with a std::decay_t<decltype(stuff_on_rhs)>.

    Note that there are other problems with some of your bind expressions, but they are not central to this issue.

    live example compiling (wrongly), live example not compiling (correctly).