CRTP и продление срока службы - PullRequest
0 голосов
/ 10 июня 2019

Мой вопрос: как заставить продление жизни работать с CRTP. Например, следующий код является абсолютно допустимым:

struct A {
    const int& ref;
};

struct B {
    const A& a;
};

int main() {
    B b{{123}};
    return b.a.ref;
}

Его CRTP-версия не является:

template <class DerivedT>
class Gettable {
public:
    int Get() const {
        return static_cast<const DerivedT*>(this)->GetImpl();
    }
};

class A : public Gettable<A> {
    friend class Gettable<A>;
public:
    A(int r) : ref{r}{}

private:
    int GetImpl() const {
        return ref;
    }

    const int& ref;
};

template <class T>
class B {
public:
    B(const Gettable<T>& gettable) : get_{gettable}{}
    int DifferentGet() const {
        return get_.Get();
    }

private:
    const Gettable<T>& get_;
};

int main() {
    B b{A{123}};
    return b.DifferentGet();
}

Проблема в том, что исходный A и его подобъект Gettable<A> существуют только до конструктора B.

У меня два вопроса:

1) Почему? Он ничем не отличается от первого случая структур, каждое время жизни известно во время компиляции, поэтому я считаю, что компилятор должен быть в состоянии продлить время жизни всех временных.

2) Есть ли хороший способ преодолеть эту проблему?

Ответы [ 2 ]

2 голосов
/ 10 июня 2019

1) Почему?

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

Он ничем не отличается от первого случая структур

Он отличается.В агрегатной инициализации нет конструктора.В этом случае компилятор знает время жизни элемента и знает, что элемент инициализируется временным.Применяется правило продления времени жизни.

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

Рассмотрим следующий пример:

struct foo {};
struct bar {
    bar(const foo& farg);
    const foo& fmem;
};
bar b({});

Следует ли продлить срок службы временного на b?Стандарт говорит, что это не так.Похоже, вы утверждаете, что так и должно быть.

Рассмотрите следующие возможные реализации конструктора:

bar::bar(const foo& farg) : fmem{farg} {}         // 1
foo fanother;
bar::bar(const foo& farg) : fmem{fanother} {}     // 2

Если реализация окажется равной 1, то вы догадались, расширение времени жизнинеобходимо.Если реализация равна 2, то мы излишне расширяем временное пространство, на которое больше не ссылаются.

Разработчики языка решили не расширять такое временное, вероятно, чтобы время жизни временных временных файлов не увеличивалось без необходимости.Как следствие, реализация 1 неверна, как и ваш пример CRTP.

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


2) Есть ли хороший способ преодолеть эту проблему?

Используйте либо int*, либо std::reference_wrapper<int> в качестве аргумента конструктора.Бывший более лаконичен, но последний обладает удобным свойством отсутствия нулевого представления.Это должно усложнить случайное связывание свисающей ссылки.В любом случае, тщательно документируйте, что указанный объект должен быть действительным при вызове Get.

0 голосов
/ 11 июня 2019

Я считаю, что самое общее решение - что-то вроде этого.Таким образом, он работает даже для многоуровневого наследования.

#include <iostream>
#include <utility>
#include <type_traits>

struct NullType {};

// Helper class for casting
template <class Derived>
class DerivedCaster {
protected:
    Derived* GetDerived() {
        return static_cast<Derived*>(this);
    }

    const Derived* GetDerived() const {
        return static_cast<const Derived*>(this);
    }
};

// Matches the predicate against the types and remembers the first 
// satisfying argument
template <template <class T> class Predicate, class... Args>
struct FindFirstMatching {
    using Type = ... ; // default NullType
    static const bool has_match = ... ;
};

// Structure which gets the deepest class from CRTP inheritance chain
// by looking at the instantiated parent class template
template<typename T>
struct GetDeepest
{
    using Type = T;
};

template<template<class...> class DT, class... T>
struct GetDeepest<DT<T...>>
{
    template <class CLS>
    struct Predicate {
    static const bool value = std::is_base_of<DT<T...>, CLS>::value;
    };

    static const bool HasCRTPDerived = FindFirstMatching<Predicate, T...>::has_match;
    using DerivedT = typename FindFirstMatching<Predicate, T...>::Type;

    using Type = std::conditional_t<HasCRTPDerived, typename GetDeepest<DerivedT>::Type, DT<T...>>;
};

// First abstract class
template <class DerivedT>
class Gettable : public DerivedCaster<DerivedT> {
public:
    int Get() const {
        return DerivedCaster<DerivedT>::GetDerived()->GetImpl();
    }
};

// Second abstract class
template <class DerivedT>
class Incrementable : public DerivedCaster<DerivedT>,
              public Gettable<Incrementable<DerivedT>> {
    friend class Gettable<Incrementable<DerivedT>>;
public:
    int Increment() const {
        return ++(this->Get());
    }

private:
    int GetImpl() const {
        return DerivedCaster<DerivedT>::GetDerived()->GetImpl() + 100;
    }
};

// non-abstract class
class A : public Incrementable<A> {
    friend class Incrementable<A>;
public:
    A(int r) : ref_{r}{}

private:
    int GetImpl() const {
        return ref_;
    }

    int ref_;
};

// Helper to get the copy of the underlying non-abstract class
template <class T>
auto GetDeepestLevelCopy(const T& arg) {
    return static_cast<const typename GetDeepest<T>::Type&>(arg);
}

// Some other class which wants a copy
template <class T>
class B {
public:
    B(const Gettable<T>& gettable) : get_{GetDeepestLevelCopy(gettable)}{}
    int DifferentGet() const {
        return get_.Get();
    }

private:
    typename GetDeepest<Gettable<T>>::Type get_;
};

int main() {
    static_assert(std::is_same_v<GetDeepest<Gettable<Incrementable<A>>>::Type, A>);
    static_assert(std::is_same_v<decltype(GetDeepestLevelCopy(std::declval<Gettable<Incrementable<A>>>())), A>);

    B b{A{123}};
    std::cout << b.DifferentGet() << "\n";
    // prints 223
    return 0;
}

Это выглядит чудовищно, но я не знаю, есть ли лучшее решение.

...