The following code snippet demonstrates what I would like to achieve, namely creating two template specializations (well, here it's a main template and a specialization), one which will be used for non-const member functions and one for const member functions:
// instantiate for non-const member functions
template <typename C, void(C::*F)()>
struct A {};
// instantiate for const member functions
template <typename C, void(C::*F)() const>
struct A<C const, F> {};
struct foo
{
void bar() const {}
typedef A<foo const, &foo::bar> bar_type;
void baz() {}
typedef A<foo, &foo::baz> baz_type;
};
While this code compiles fine using gcc 4.7, Intel 13.0 and MSVC 2012, it fails to compile using Clang 3.3 or Comeau 4.3.10.1. I trust Clang is actually right.
How to rewrite this code to make it standards conforming (i.e. compiling with Clang)?
Here is the compilation error:
test_6.cpp:22:26: error: non-type template argument of type 'void (foo::*)() const' cannot be converted to a value of type 'void (const foo::*)()'
typedef A<foo const, &foo::bar> bar_type;
^~~~~~~~~
test_6.cpp:7:33: note: template parameter is declared here
template <typename C, void (C::*F)()>
^
If you make the member function type a template parameter then you can specialize the template for different member function types:
template <typename C, typename F, F>
struct A; // undefined
template <typename C, void(C::*f)()>
struct A<C, void(C::*)(), f> {};
template <typename C, void(C::*f)() const>
struct A<C const, void(C::*)() const, f> {};
struct foo
{
void bar() const {}
typedef A<foo const, decltype(&foo::bar), &foo::bar> bar_type;
void baz() {}
typedef A<foo, decltype(&foo::baz), &foo::baz> baz_type;
};
That's not how (partial) specialization works. When you provide an argument in a specialization, it has to match the corresponding parameter's kind.
Your primary template has a pointer-to-non-const-member-function (PTNCMF) parameter, expecting a template argument of that kind. Your partial specialization, however, passes a pointer-to-const-member-function (PTCMF) as the argument, creating a mismatch. A PTCMF isn't convertible to a PTNCMF, so the partial specialization isn't valid.
So much for the problem, on to the solution. You need to seperate the type of the argument from the actual argument. One way would be the following, simply asserting that a const class type is only ever matched with a PTCMF.
#include <type_traits>
template<class Fty>
struct is_ptcmf : std::false_type{};
template<class C, class R, class... Args>
struct is_ptcmf<R (C::*)(Args...) const> : std::true_type{};
template<class C, class Fty, Fty F>
struct A{
static_assert(std::is_const<C>() == is_ptcmf<Fty>(), "Must pair const with const.");
};
Live example.
Usage would then be A<foo, decltype(&foo::bar), &foo::bar>. If you think there is some redundancy, I agree, but there is no nice way yet to get rid of it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With