Как удалить дублирование кода между похожими константными и неконстантными функциями-членами?

Допустим, у меня есть следующее class X где я хочу вернуть доступ к внутреннему члену:

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    Z& Z(size_t index)
    {
        // massive amounts of code for validating index

        Z& ret = vecZ[index];

        // even more code for determining that the Z instance
        // at index is *exactly* the right sort of Z (a process
        // which involves calculating leap years in which
        // religious holidays fall on Tuesdays for
        // the next thousand years or so)

        return ret;
    }
    const Z& Z(size_t index) const
    {
        // identical to non-const X::Z(), except printed in
        // a lighter shade of gray since
        // we're running low on toner by this point
    }
};

Две функции-члена X::Z() а также X::Z() const иметь одинаковый код внутри фигурных скобок. Это дублирующий код и может вызвать проблемы с обслуживанием для длинных функций со сложной логикой.

Есть ли способ избежать этого дублирования кода?

21 ответ

Решение

Да, можно избежать дублирования кода. Вам необходимо использовать функцию-член const, чтобы иметь логику и заставить неконстантную функцию-член вызывать функцию-член const и повторно приводить возвращаемое значение к неконстантной ссылке (или указателю, если функции возвращают указатель):

class X
{
   std::vector<Z> vecZ;

public:
   const Z& Z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& Z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).Z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.Z(index) );
   }
 #endif
};

ПРИМЕЧАНИЕ. Важно, чтобы вы НЕ помещали логику в неконстантную функцию и чтобы константная функция вызывала неконстантную функцию - это может привести к неопределенному поведению. Причина в том, что экземпляр константного класса приводится как неконстантный экземпляр. Неконстантная функция-член может случайно изменить класс, что в стандартных состояниях C++ приведет к неопределенному поведению.

Для подробного объяснения, пожалуйста, смотрите заголовок "Избегайте дублирования в const и неconst Функция-член ", на стр. 23, в пункте 3" Использование const когда это возможно ", в Effective C++, 3d ed by Scott Meyers, ISBN-13: 9780321334879.

альтернативный текст

Вот решение Мейерса (упрощенно):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

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

C++17 обновил лучший ответ на этот вопрос:

T const & f() const {
    return something_complicated();
}
T & f() {
    return const_cast<T &>(std::as_const(*this).f());
}

Это имеет то преимущество, что:

  • Очевидно, что происходит
  • Имеет минимальные накладные расходы кода - он помещается в одну строку
  • Трудно ошибиться (можно только выбросить volatile случайно, но volatile редкий классификатор)

Если вы хотите пройти полный путь вычета, это может быть достигнуто с помощью вспомогательной функции

template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
    return const_cast<T &>(value);
}
template<typename T>
void as_mutable(T const &&) = delete;

Теперь вы даже не можете испортить volatileи использование выглядит как

T & f() {
    return as_mutable(std::as_const(*this).f());
}

Я думаю, что решение Скотта Мейерса может быть улучшено в C++11 с помощью вспомогательной функции tempate. Это делает намерение намного более очевидным и может быть повторно использовано многими другими получателями.

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

Эта вспомогательная функция может быть использована следующим образом.

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

Первым аргументом всегда является указатель this. Второй - указатель на функцию-член для вызова. После этого может быть передано произвольное количество дополнительных аргументов, чтобы они могли быть переданы в функцию. Это требует C++11 из-за шаблонов с переменным числом аргументов.

C++23 обновил лучший ответ на этот вопрос благодаря выводу :

      struct s {
    auto && f(this auto && self) {
        // all the common code goes here
    }
};

Шаблон единственной функции может быть вызван как обычная функция-член и определяет для вас правильный ссылочный тип. Никакого преобразования, чтобы ошибиться, никакого написания нескольких функций для чего-то, что концептуально является одним.

Хороший вопрос и хорошие ответы. У меня есть другое решение, которое не использует приведения:

class X {

private:

    std::vector<Z> v;

    template<typename InstanceType>
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
        // massive amounts of code for validating index
        // the instance variable has to be used to access class members
        return instance.v[i];
    }

public:

    const Z& get(std::size_t i) const {
        return get(*this, i);
    }

    Z& get(std::size_t i) {
        return get(*this, i);
    }

};

Однако у него есть уродство, требующее статического члена и необходимость использования instance переменная внутри него.

Я не учел все возможные (негативные) последствия этого решения. Пожалуйста, дайте мне знать, если таковые имеются.

Немного более многословно, чем Мейерс, но я мог бы сделать это:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

Закрытый метод обладает нежелательным свойством, что он возвращает неконстантный Z& для константного экземпляра, поэтому он является закрытым. Частные методы могут нарушать инварианты внешнего интерфейса (в этом случае желаемый инвариант - "объект const не может быть изменен через ссылки, полученные через него на объекты, которые он имеет -a").

Обратите внимание, что комментарии являются частью шаблона - интерфейс _getZ указывает на то, что его никогда нельзя назвать действительным (за исключением средств доступа, очевидно): в любом случае это не представляется очевидным преимуществом, потому что вводится еще 1 символ и не будет результат в меньшем или более быстром коде. Вызов метода эквивалентен вызову одного из методов доступа с const_cast, и вам бы этого тоже не хотелось. Если вы беспокоитесь о том, чтобы сделать ошибки очевидными (и это справедливая цель), тогда назовите это const_cast_getZ вместо _getZ.

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

[Редактировать: Кевин справедливо указал, что _getZ, возможно, захочет вызвать еще один метод (скажем, generateZ), который специализируется на const так же, как getZ. В этом случае _getZ увидит const Z& и будет вынужден const_cast перед возвратом. Это по-прежнему безопасно, так как шаблонный аксессуар контролирует все, но не совсем очевидно, что это безопасно. Более того, если вы сделаете это, а затем измените generateZ, чтобы он всегда возвращал const, вам также нужно изменить getZ, чтобы он всегда возвращал const, но компилятор не скажет вам, что вы это делаете.

Это последнее замечание о компиляторе также верно для рекомендованного Мейерса шаблона, но первое замечание о неочевидном const_cast - нет. Таким образом, в итоге я думаю, что если _getZ окажется нужным const_cast для своего возвращаемого значения, то этот шаблон теряет большую часть своего значения по сравнению с Мейерсом. Поскольку он также страдает недостатками по сравнению с Мейерсом, я думаю, что я бы переключился на его в этой ситуации. Рефакторинг от одного к другому прост - он не влияет на любой другой допустимый код в классе, так как только недопустимый код и шаблон вызывают _getZ.]

Вы также можете решить это с помощью шаблонов. Это решение немного уродливо (но уродство скрыто в файле.cpp), но оно обеспечивает проверку константности компилятором и не дублирует код.

.h файл:

#include <vector>

class Z
{
    // details
};

class X
{
    std::vector<Z> vecZ;

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

Файл.cpp:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

Основной недостаток, который я вижу, состоит в том, что, поскольку вся сложная реализация метода находится в глобальной функции, вам нужно либо получить члены X, используя открытые методы, такие как GetVector() выше (из которых всегда должен быть константная и неконстантная версия) или вы можете сделать эту функцию другом. Но я не люблю друзей.

[Редактировать: удалено ненужное включение cstdio, добавленное во время тестирования.]

Для тех (как я), кто

  • использовать с ++17
  • хотите добавить наименьшее количество шаблона/ повторения и
  • не против использования макроса (в ожидании мета-классов...),

вот еще один дубль:

#include <utility>
#include <type_traits>

template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};

#define NON_CONST(func)                                                     \
    template <typename... T>                                                \
    auto func(T&&... a) -> typename NonConst<decltype(func(a...))>::type {  \
        return const_cast<decltype(func(a...))>(                            \
            std::as_const(*this).func(std::forward<T>(a)...));              \
    }

В основном это смесь ответов @Pait, @DavidStone и @sh1. То, что он добавляет в таблицу, это то, что вы получаете только одну дополнительную строку кода, которая просто называет функцию (но без аргумента или дублирования возвращаемого типа):

class X
{
    const Z& get(size_t index) const { ... }
    NON_CONST(get)
};

Примечание: gcc не может скомпилировать это до 8.1, clang-5 и выше, а также MSVC-19 счастливы (согласно исследователю компилятора).

Вот версия C++17 статической вспомогательной функции шаблона с дополнительным тестом SFINAE.

#include <type_traits>

#define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )

class Foobar {
private:
    int something;

    template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
    static auto& _getSomething(FOOBAR& self, int index) {
        // big, non-trivial chunk of code...
        return self.something;
    }

public:
    auto& getSomething(int index)       { return _getSomething(*this, index); }
    auto& getSomething(int index) const { return _getSomething(*this, index); }
};

Полная версия: https://godbolt.org/z/mMK4r3

Хотя в большинстве ответов здесь предлагается использовать const_cast, В CppCoreGuidelines есть раздел об этом:

Вместо этого предпочитайте делиться реализациями. Обычно у вас может просто быть неконстантная функция, вызывающая константную функцию. Однако при наличии сложной логики это может привести к следующему шаблону, который по-прежнему использует const_cast:

      class Foo {
public:
    // not great, non-const calls const version but resorts to const_cast
    Bar& get_bar()
    {
        return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
    }
    const Bar& get_bar() const
    {
        /* the complex logic around getting a const reference to my_bar */
    }
private:
    Bar my_bar;
};

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

Вместо этого лучше поместить общий код в общую вспомогательную функцию и сделать его шаблоном, чтобы он выводил const. Это вообще не использует const_cast:

      class Foo {
public:                         // good
          Bar& get_bar()       { return get_bar_impl(*this); }
    const Bar& get_bar() const { return get_bar_impl(*this); }
private:
    Bar my_bar;

    template<class T>           // good, deduces whether T is const or non-const
    static auto& get_bar_impl(T& t)
        { /* the complex logic around getting a possibly-const reference to my_bar */ }
};

Примечание. Не выполняйте большую независимую работу внутри шаблона, так как это приведет к раздуванию кода. Например, дальнейшее улучшение могло бы состоять в том, если бы get_bar_impl полностью или частично мог быть независимым и выделенным в общую функцию, не являющуюся шаблоном, для потенциально большого уменьшения размера кода.

Как насчет того, чтобы переместить логику в закрытый метод и выполнять только операции "получить ссылку и вернуть" внутри геттеров? На самом деле, я был бы довольно смущен статическими и константными приведениями внутри простой функции получения, и я бы счел это уродливым, за исключением крайне редких обстоятельств!

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

Я написал макрос FROM_CONST_OVERLOAD() который может быть помещен в неконстантную функцию для вызова константной функции.

Пример использования:

class MyClass
{
private:
    std::vector<std::string> data = {"str", "x"};

public:
    // Works for references
    const std::string& GetRef(std::size_t index) const
    {
        return data[index];
    }

    std::string& GetRef(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetRef(index) );
    }


    // Works for pointers
    const std::string* GetPtr(std::size_t index) const
    {
        return &data[index];
    }

    std::string* GetPtr(std::size_t index)
    {
        return FROM_CONST_OVERLOAD( GetPtr(index) );
    }
};

Простая и многоразовая реализация:

template <typename T>
T& WithoutConst(const T& ref)
{
    return const_cast<T&>(ref);
}

template <typename T>
T* WithoutConst(const T* ptr)
{
    return const_cast<T*>(ptr);
}

template <typename T>
const T* WithConst(T* ptr)
{
    return ptr;
}

#define FROM_CONST_OVERLOAD(FunctionCall) \
  WithoutConst(WithConst(this)->FunctionCall)

Пояснение:

Как указано во многих ответах, типичный шаблон, позволяющий избежать дублирования кода в неконстантной функции-члене, таков:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

Многие из этих шаблонов можно избежать, используя вывод типа. Первый,const_cast может быть инкапсулирован в WithoutConst(), который определяет тип своего аргумента и удаляет квалификатор const. Во-вторых, аналогичный подход можно использовать вWithConst() констатировать this указатель, который позволяет вызвать метод, перегруженный константой.

Остальное - простой макрос, который ставит перед вызовом правильно определенный this->и удаляет const из результата. Поскольку выражение, используемое в макросе, почти всегда является простым вызовом функции с перенаправленными аргументами 1:1, недостатки макросов, такие как множественное вычисление, не проявляются. Многоточие и__VA_ARGS__ также можно использовать, но в этом нет необходимости, потому что запятые (как разделители аргументов) заключены в круглые скобки.

У этого подхода есть несколько преимуществ:

  • Минимальный и естественный синтаксис - просто оберните вызов в FROM_CONST_OVERLOAD( )
  • Дополнительная функция-член не требуется
  • Совместим с C++98
  • Простая реализация, без метапрограммирования шаблонов и нулевых зависимостей
  • Расширяемость: могут быть добавлены другие константные отношения (например, const_iterator, std::shared_ptr<const T>, так далее.). Для этого просто перегрузитеWithoutConst() для соответствующих типов.

Ограничения: это решение оптимизировано для сценариев, в которых неконстантная перегрузка делает то же самое, что и константная перегрузка, поэтому аргументы можно пересылать 1:1. Если ваша логика отличается, и вы не вызываете версию const черезthis->Method(args), вы можете рассмотреть другие подходы.

Я бы предложил шаблон статической функции частного помощника, например:

class X
{
    std::vector<Z> vecZ;

    // ReturnType is explicitly 'Z&' or 'const Z&'
    // ThisType is deduced to be 'X' or 'const X'
    template <typename ReturnType, typename ThisType>
    static ReturnType Z_impl(ThisType& self, size_t index)
    {
        // massive amounts of code for validating index
        ReturnType ret = self.vecZ[index];
        // even more code for determining, blah, blah...
        return ret;
    }

public:
    Z& Z(size_t index)
    {
        return Z_impl<Z&>(*this, index);
    }
    const Z& Z(size_t index) const
    {
        return Z_impl<const Z&>(*this, index);
    }
};

Это обманывает использовать препроцессор?

struct A {

    #define GETTER_CORE_CODE       \
    /* line 1 of getter code */    \
    /* line 2 of getter code */    \
    /* .....etc............. */    \
    /* line n of getter code */       

    // ^ NOTE: line continuation char '\' on all lines but the last

   B& get() {
        GETTER_CORE_CODE
   }

   const B& get() const {
        GETTER_CORE_CODE
   }

   #undef GETTER_CORE_CODE

};

Это не так красиво, как шаблоны или приведение, но оно делает ваше намерение ("эти две функции должны быть идентичными") довольно явным.

Я сделал это для друга, который по праву оправдывал использование const_cast... не зная об этом, я, вероятно, сделал бы что-то вроде этого (не очень элегантно):

#include <iostream>

class MyClass
{

public:

    int getI()
    {
        std::cout << "non-const getter" << std::endl;
        return privateGetI<MyClass, int>(*this);
    }

    const int getI() const
    {
        std::cout << "const getter" << std::endl;
        return privateGetI<const MyClass, const int>(*this);
    }

private:

    template <class C, typename T>
    static T privateGetI(C c)
    {
        //do my stuff
        return c._i;
    }

    int _i;
};

int main()
{
    const MyClass myConstClass = MyClass();
    myConstClass.getI();

    MyClass myNonConstClass;
    myNonConstClass.getI();

    return 0;
}

Я придумал макрос, который автоматически генерирует пары константных / неконстантных функций.

class A
{
    int x;    
  public:
    MAYBE_CONST(
        CV int &GetX() CV {return x;}
        CV int &GetY() CV {return y;}
    )

    //   Equivalent to:
    // int &GetX() {return x;}
    // int &GetY() {return y;}
    // const int &GetX() const {return x;}
    // const int &GetY() const {return y;}
};

См. Конец ответа для реализации.

Аргумент MAYBE_CONSTдублируется. В первом экземпляреCVзаменяется ничем; а во втором экземпляре он заменен наconst.

Нет предела тому, сколько раз CV может появиться в аргументе макроса.

Хотя есть небольшое неудобство. ЕслиCV появляется внутри скобок, перед этой парой скобок должен стоять префикс CV_IN:

// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )

// Works, expands to
//         int &foo(      int &);
//   const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )

Реализация:

#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,

#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )

#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq

#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__

#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)

#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)

Реализация до C++20, которая не поддерживает CV_IN:

#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((

#define IMPL_MC(seq) \
    IMPL_MC_end(IMPL_MC_a seq) \
    IMPL_MC_end(IMPL_MC_const_0 seq)

#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end

#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end

#define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end

Как правило, функции-члены, для которых вам нужны константные и неконстантные версии, являются геттерами и сеттерами. В большинстве случаев они являются однострочными, поэтому дублирование кода не является проблемой.

Чтобы добавить к решению jwfearn и kevin, вот соответствующее решение, когда функция возвращает shared_ptr:

struct C {
  shared_ptr<const char> get() const {
    return c;
  }
  shared_ptr<char> get() {
    return const_pointer_cast<char>(static_cast<const C &>(*this).get());
  }
  shared_ptr<char> c;
};

Не нашел то, что искал, поэтому я прокатил пару своих...

Это немного многословно, но имеет преимущество обработки сразу нескольких перегруженных методов с одним и тем же именем (и типом возврата):

struct C {
  int x[10];

  int const* getp() const { return x; }
  int const* getp(int i) const { return &x[i]; }
  int const* getp(int* p) const { return &x[*p]; }

  int const& getr() const { return x[0]; }
  int const& getr(int i) const { return x[i]; }
  int const& getr(int* p) const { return x[*p]; }

  template<typename... Ts>
  auto* getp(Ts... args) {
    auto const* p = this;
    return const_cast<int*>(p->getp(args...));
  }

  template<typename... Ts>
  auto& getr(Ts... args) {
    auto const* p = this;
    return const_cast<int&>(p->getr(args...));
  }
};

Если у вас есть только один const метод на имя, но все равно множество методов для дублирования, тогда вы можете предпочесть это:

  template<typename T, typename... Ts>
  auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
    return const_cast<T*>((this->*f)(args...));
  }

  int* getp_i(int i) { return pwrap(&C::getp_i, i); }
  int* getp_p(int* p) { return pwrap(&C::getp_p, p); }

К сожалению, это выходит из строя, как только вы начинаете перегружать имя (список аргументов аргумента указателя функции кажется нерешенным в этот момент, поэтому он не может найти соответствие для аргумента функции). Хотя вы также можете выбрать способ выхода из этого:

  template<typename... Ts>
  auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }

Но ссылаться на аргументы const Метод не может сравниться с внешне-значимыми аргументами шаблона, и он ломается. Не уверен почему. Вот почему

Эта статья о DDJ показывает способ использования шаблонов, который не требует использования const_cast. Для такой простой функции она действительно не нужна.

boost:: any_cast (в какой-то момент он больше не использует) использует const_cast из const-версии, вызывая неконстантную версию, чтобы избежать дублирования. Вы не можете навязать семантику const для неконстантной версии, поэтому вы должны быть очень осторожны с этим.

В конце концов, некоторое дублирование кода вполне допустимо, если два фрагмента находятся прямо друг над другом.

Другие вопросы по тегам