CRTP с защищенным производным членом

В CRTP-шаблоне мы сталкиваемся с проблемами, если хотим сохранить функцию реализации в производном классе как защищенную. Мы должны либо объявить базовый класс как друга производного класса, либо использовать что-то вроде этого (я не пробовал метод в связанной статье), Есть ли другой (простой) способ, позволяющий сохранить функцию реализации в производном классе как защищенный?

Изменить: Вот простой пример кода:

template<class D> 
class C {
public:
    void base_foo()
    {
        static_cast<D*>(this)->foo();
    }
};


class D:  public C<D> {
protected: //ERROR!
    void foo() {
    }   
};

int main() {
    D d;
    d.base_foo();
    return 0;
}

Приведенный выше код дает error: ‘void D::foo()’ is protected с g++ 4.5.1, но компилируется, если protected заменяется на public.

Ответы

Ответ 1

Это не проблема вообще и решается одной строкой в ​​производном классе:

friend class Base< Derived >;

#include <iostream>

template< typename PDerived >
class TBase
{
 public:
  void Foo( void )
  {
   static_cast< PDerived* > ( this )->Bar();
  }
};

class TDerived : public TBase< TDerived >
{
  friend class TBase< TDerived > ;
 protected:
  void Bar( void )
  {
   std::cout << "in Bar" << std::endl;
  }
};

int main( void )
{
 TDerived lD;

 lD.Foo();

 return ( 0 );
}

Ответ 2

Как и рекомендует lapk, проблему можно решить с помощью простого объявления класса друга:

class D:  public C<D> {
    friend class C<D>;      // friend class declaration
protected:
    void foo() {
    }
};

Однако это открывает все защищенные/закрытые члены производного класса и требует настраиваемого кода для каждого объявления производного класса.

Следующее решение основано на связанной статье:

template<class D>
class C {
public:
    void base_foo() { Accessor::base_foo(derived()); }
    int base_bar()  { return Accessor::base_bar(derived()); }

private:
    D& derived() { return *(D*)this; }

    // accessor functions for protected functions in derived class
    struct Accessor : D
    {
        static void base_foo(D& derived) {
            void (D::*fn)() = &Accessor::foo;
            (derived.*fn)();
        }
        static int base_bar(D& derived) {
            int (D::*fn)() = &Accessor::bar;
            return (derived.*fn)();
        }
    };
};

class D : public C<D> {
protected: // Success!
    void foo() {}
    int bar() { return 42; }
};

int main(int argc, char *argv[])
{
    D d;
    d.base_foo();
    int n = d.base_bar();
    return 0;
}

PS: Если вы не доверяете своему компилятору в оптимизации ссылок, вы можете заменить функцию derived() на следующий #define (в результате на MSVC 2013 было сокращено на 20% меньше строк кода разборки):

    int base_bar() { return Accessor::base_bar(_instance_ref); }

    private:
    #define _instance_ref *static_cast<D*>(this)   //D& derived() { return *(D*)this; }

Ответ 3

После некоторых я пришел с решением, которое работает событие для частных членов шаблонных производных классов. Он не решает проблему не предоставления всем членам производного класса базы, поскольку он использует объявление friend для всего класса. С другой стороны, для простого случая это не требует повторения базового имени и параметров шаблона и всегда будет работать.

Сначала простой случай, когда производное не является шаблоном. База принимает дополнительный параметр шаблона void, чтобы показать, что все еще работает в случае дополнительных параметров шаблона базы. Единственный необходимый, согласно CRTP, это typename Derived.

//Templated variadic base
template <typename Derived, typename...>
struct Interface
{
    using CRTP = Interface; //Magic!
    void f() { static_cast<Derived*>(this)->f(); }
};

//Simple usage of the base with extra types
//This can only be used when the derived is NON templated
class A : public Interface<A, void>
{
    friend CRTP;
    void f() {}
};

Единственное, что нужно для этого, - это объявление using CRTP = Interface; в базе и объявление friend CRTP; в производном.

Для случая, когда производная сама является шаблонной, ситуация сложнее. Мне понадобилось некоторое время, чтобы прийти к решению, и я уверен, что оно все еще не идеально.

Большая часть магии происходит внутри этих шаблонов:

namespace CRTP
{
    template <template <typename, typename...> class _Base, typename _Derived, typename... _BaseArgs>
    struct Friend { using Base = _Base<_Derived, _BaseArgs...>; };

    template <template <typename, typename...> class _Base, typename ..._BaseArgs>
    struct Base
    {
        template <template <typename...> class _Derived, typename... _DerivedArgs>
        struct Derived : public _Base<_Derived<_DerivedArgs...>, _BaseArgs...> {};
    };
}

Их использование более или менее просто. Для двух использованных выше шаблонов необходимо выполнить несколько шагов.

Во-первых, при наследовании в производном классе необходимо указать базовый класс наследуемого и его необязательные параметры. Это делается с помощью CRTP::Base<MyBase, BaseOptional....>, где MyBase - это имя класса, используемого для CRTP, а BaseOptional... - это параметры шаблона, которые передаются в базовый класс как есть, непосредственно после передачи нашего производного класса, который предоставляется на следующем этапе. Если базовый класс не принимает никаких дополнительных параметров шаблона, они могут быть полностью опущены: CRTP::Base<MyBase>.

Следующим шагом является представление производного класса (весь смысл CRTP). Это можно сделать, следуя приведенному выше CRTP::Base<...> с помощью ::Derived<ThisDerived, DerivedOptional...>. Где ThisDerived - это класс, в котором он определен, а DerivedOptional... - все параметры шаблона, объявленные в объявлении этого класса template. Необязательные параметры должны быть указаны точно, как они указаны в объявлении класса template.

Последний шаг - объявить базовый класс как friend. Это делается путем объявления friend typename CRTP::Friend<MyBase, ThisDerived, BaseOptional...>::Base где-то в классе. Шаблонные параметры BaseOptional... должны повторяться в точности так, как они появляются в CRTP::Base<MyBase, BaseOptional...>, который унаследован от.

Ниже приведен пример использования шаблонного производного, когда основание не зависит от шаблонных типов (но оно все еще может принимать другие параметры шаблона, void в этом примере).

//Templated derived with extra, non-dependant types, passed to the base
//The arguments passed to CRTP::Base::Derived<, ARGS> must exactly match
//  the template
template <typename T, typename... Args>
class B : public CRTP::Base<Interface, void>::Derived<B, T, Args...>
{
    friend typename CRTP::Friend<Interface, B, void>::Base;
    void f() {}
};

Далее приведен пример, когда база зависит от параметров шаблона производного. Единственное отличие от предыдущего примера - ключевое слово template. Эксперимент показывает, что, если ключевое слово указано для предыдущего, независимого случая, код также соответствует требованиям.

//Templated derived with extra dependant types passed to the base
//Notice the addition of the "template" keyword
template <typename... Args>
class C : public CRTP::Base<Interface, Args...>::template Derived<C, Args...>
{
    friend typename CRTP::Friend<Interface, C, Args...>::Base;
    void f() {}
};

Обратите внимание, что эти шаблоны не работают для производных классов без шаблонов. Я обновлю этот ответ, когда найду решение, чтобы единый синтаксис мог использоваться для всех случаев. Самое близкое, что можно сделать, это просто использовать какой-то поддельный параметр шаблона. Обратите внимание, что он все еще должен быть назван и передан в механизм CRTP. Например:

template <typename Fake = void>
class D : public CRTP::Base<Interface>::Derived<D, Fake>
{
    friend typename CRTP::Friend<Interface, D>::Base;
    void f() {}
};

Обратите внимание, что A, B, C и D объявляется как class. То есть все их участники являются частными.

Ниже приведен код, который использует перечисленные выше классы.

template <typename... Args>
void invoke(Interface<Args...> & base)
{
    base.f();
}

int main(int, char *[])
{
    {
        A derived;

        //Direct invocation through cast to base (derived.f() is private)
        static_cast<A::CRTP &>(derived).f();

        //Invocation through template function accepting the base
        invoke(derived);
    }

    {
        B<int> derived;
        static_cast<B<int>::CRTP &>(derived).f();
        invoke(derived);
    }

    {
        C<void> derived;
        static_cast<C<void>::CRTP &>(derived).f();
        invoke(derived);
    }

    {
        D<void> derived;
        static_cast<D<>::CRTP &>(derived).f();
        invoke(derived);
    }

    return 0;
}

Автономная шаблонная функция invoke работает для любого класса, полученного из базы. Также показано, как привести производное к базе без необходимости фактически указывать имя базы. Удивительно, но это не зависит от системных заголовков.

Полный код доступен здесь: https://gist.github.com/equilibr/b27524468a0519aad37abc060cb8bc2b

Комментарии и исправления приветствуются.