Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rewrite this to make it conforming to the C++ Standard

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)()>
                                ^
like image 711
hkaiser Avatar asked Dec 12 '25 08:12

hkaiser


2 Answers

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;
};
like image 73
Jonathan Wakely Avatar answered Dec 13 '25 20:12

Jonathan Wakely


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.

like image 37
Xeo Avatar answered Dec 13 '25 20:12

Xeo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!