Reputation: 4173
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:
typedef T value_type;
) or to pull them in from the base class (e.g. typedef typename A<T>::value_type value_type;
, or in C++11 using typename A<T>::value_type;
)? (Note: I'm hedging on C++03 compatibility right now, hence the avoidance of using
.) There was some inconclusive discussion here: Use typedef/using from templated base class in derived class The reason I ask is that if typedef T value_type;
is preferred anyway, I don't need to worry so much.#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
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
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