Ross Bencina
Ross Bencina

Reputation: 4173

Function template specialization failure: coding error or MSVC2013 bug?

The code below compiles correctly with g++ 4.8.1 (mingw), and a variety of recent clang and gcc versions on http://gcc.godbolt.org/, however with MSVC2013 Update 4 it fails, apparently due to the typedef typename A<T>::value_type value_type; line. The compiler gives the following error:

x.cpp(30): error C2893: Failed to specialize function template 'void B<C,int>::bar(void)' With the following template arguments: 'MemberFn=void C::baz(int)'

The simpler typedef typedef T value_type; works.

Am I doing something wrong? Or is this a known bug in the Microsoft C++ compiler?

Supplementary question:

#include <cstdio>

template <typename T>
struct A {
    typedef A<T> base_type;
    typedef T value_type;
};

template <typename Derived, typename T>
struct B : public A<T> {
    typedef Derived derived_type;
    //typedef T value_type; // this works
    typedef typename A<T>::value_type value_type; // this fails in MSVC 2013
    //using typename A<T>::value_type; // this fails in MSVC 2013 too

    template<void (derived_type::*MemberFn)(value_type) >
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

struct C : public B<C, int> {
    void baz(int i)
    {
        std::printf("baz(%d)\n", i);
    }

    void foo()
    {
        bar<&C::baz>();
    }
};

int main(int, char *[])
{
    C c;
    c.foo();
}

Update #1: This is a reduced test case from a framework. I am not asking for general critique of the structure. I do not expect the structure to make sense without context.

Update #2: Here is a related question discussing whether using is valid in combination with typename: C++ template inheritance issue with base types

Update #3: I have filed a public bug report at Microsoft Connect. If you can reproduce the issue, and believe that it is a bug, please upvote the bug: https://connect.microsoft.com/VisualStudio/feedback/details/1740423

Upvotes: 3

Views: 366

Answers (2)

Simon Kraemer
Simon Kraemer

Reputation: 5680

I have a possible solution (I can only test this with VS2015)

If you use the base type as a template parameter itself it is resolved and you can access the value_type.

template <typename T>
struct A {
    typedef T value_type;
    typedef A<T> base_type;
};

template <typename Derived, typename T, typename Base = A<T>>
struct B : public Base
{
    typedef Derived derived_type;
    typedef void (derived_type::*member_func_type)(typename Base::value_type);

    template<member_func_type MemberFn>
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};


struct C : public B<C, int> 
{
    void baz(int i)
    {
        std::printf("baz(%d)\n", i);
    }

    void foo()
    {
        bar<&C::baz>();
    }
};

Or the solution I would prefer:

template <typename T>
struct A {
    typedef A<T>    base_type;
    typedef T       value_type;
};

template <typename Derived, typename T, typename Base = A<T>>
struct B : public Base
{
    typedef Derived                     derived_type;
    typedef typename Base::base_type    base_type;
    typedef typename Base::value_type   value_type;

    typedef void (derived_type::*member_func_type)(value_type);

    template<member_func_type MemberFn>
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

I must admit that this solution has its flaws, like the ability to override the 3rd template parameter with something else. At least the other base class needs to have base_type and value_type declared.


EDIT: Using a static_assert can prevent changing the Base template parameter.

template <typename Derived, typename T, typename Base = A<T> >
struct B : public Base
{
    static_assert(std::is_same<Base, typename A<T>>::value, "Redefinition of template parameter Base is not allowed");

    typedef Derived                     derived_type;
    typedef typename Base::base_type    base_type;
    typedef typename Base::value_type   value_type;

    typedef void (derived_type::*member_func_type)(value_type);

    template<member_func_type MemberFn>
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

Example:

template <typename T>
struct D {
    typedef D<T>    base_type;
    typedef T       value_type;
};

struct E : public B<C, int, D<int>>
{
};

Result:

error C2338: Redefinition of template parameter Base is not allowed

UPDATE:

Changing the order of the template parameters of B changes the behaviour. Here the original code with just the order of T and Derived changed.

#include <cstdio>

template <typename T>
struct A {
    typedef A<T> base_type;
    typedef T value_type;
};

template <typename T, typename Derived>
struct B : public A<T> {
    typedef Derived derived_type;
    //typedef T value_type; // this works
    typedef typename A<T>::value_type value_type; // this fails in MSVC 2013
                                                  //using typename A<T>::value_type; // this fails in MSVC 2013 too

    template<void (derived_type::*MemberFn)(value_type) >
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

struct C : public B<int, C> {
    void baz(int i)
    {
        std::printf("baz(%d)\n", i);
    }

    void foo()
    {
        bar<&C::baz>();
    }
};


int main(int, char *[])
{
    C c;
    c.foo();
}

This compiles and works fine.

I'm still not completely sure that this is a bug.


Update

So this seems to be a missing feature in MSVC. MSVC (up to 2015/14.0) doesn't seem to support "Two-phased-name-lookup".

Darran Rowe: VC hasn't implemented three C++98/03 features: two-phase name lookup, dynamic exception specifications, and export. Two-phase name lookup remains unimplemented in 2015, but it's on the compiler team's list of things to do, pending codebase modernization. Dynamic exception specifications also remain unimplemented (VC gives non-Standard semantics to throw() and ignores other forms), but they were deprecated in C++11 and nobody cares about them now that we have noexcept. It's unlikely that we'll ever implement them, and there's even been talk of removing them from C++17. Finally, export was removed in C++11.

Source C++11/14/17 Features In VS 2015 RTM

There has been a bug requesting that feature in 2012 but it was closed without a comment: support two-phase name lookup - by Ivan Sorokin

So it seems like you are doing everything right here, but MSVC just doesn't support this part of the C++ standard.

Upvotes: 1

Gilad Pellaeon
Gilad Pellaeon

Reputation: 111

Looks right. I tested it with VS2015, same error message. In fact, it should work without the typedef in B because it inherits the 'value_type' from A, so it's also in the namespace if struct B.

Upvotes: 1

Related Questions