Impedir que o usuário seja derivado de uma base incorreta de CRTP

Não consigo pensar em um título de pergunta adequado para descrever o problema. Espero que os detalhes abaixo expliquem claramente o meu problema.

Considere o seguinte código

#include <iostream>

template <typename Derived>
class Base
{
    public :

    void call ()
    {
        static_cast<Derived *>(this)->call_impl();
    }
};

class D1 : public Base<D1>
{
    public :

    void call_impl ()
    {
        data_ = 100;
        std::cout << data_ << std::endl;
    }

    private :

    int data_;
};

class D2 : public Base<D1> // This is wrong by intension
{
    public :

    void call_impl ()
    {
        std::cout << data_ << std::endl;
    }

    private :

    int data_;
};

int main ()
{
    D2 d2;
    d2.call_impl();
    d2.call();
    d2.call_impl();
}

Ele irá compilar e executar embora a definição deD2 está intencionalmente errado. A primeira chamadad2.call_impl() irá produzir alguns bits aleatórios que é esperado comoD2::data_ não foi inicializado. A segunda e terceira chamadas serão todas100 para odata_.

Eu entendo por que ele irá compilar e executar, me corrija se eu estiver errado.

Quando fazemos a ligaçãod2.call(), a chamada é resolvida paraBase<D1>::call, e isso vai lançarthis paraD1 e ligueD1::call_impl. PorqueD1 é de fato forma derivadaBase<D1>, então o elenco está bem em tempo de compilação.

Em tempo de execução, após o elenco,this, enquanto é verdadeiramente umD2 objeto é tratado como se fosseD1e a chamada paraD1::call_impl vai modificou os bits de memória que deveriam serD1::data_e saída. Nesse caso, esses bits aconteceram ondeD2::data_ está. Eu acho que o segundod2.call_impl() também deve ser um comportamento indefinido, dependendo da implementação do C ++.

O ponto é, este código, enquanto intensionalmente errado, não dará nenhum sinal de erro ao usuário. O que realmente estou fazendo no meu projeto é que eu tenho uma classe base CRTP que atua como um mecanismo de despacho. Outra classe na biblioteca acessa a interface da classe base CRTP, digamoscallecall vai despachar paracall_dispatch que pode ser implementação padrão de classe base ou implementação de classe derivada. Tudo isso funcionará bem se a classe derivada definida pelo usuário, digamosD, é de fato derivado deBase<D>. Ele aumentará o erro de tempo de compilação se for derivado deBase<Unrelated> OndeUnrelated não é derivado deBase<Unrelated>. Mas isso não impedirá o código de gravação do usuário como acima.

O usuário usa a biblioteca derivando da classe CRTP de base e fornecendo alguns detalhes de implementação. Há certamente outras alternativas de design que podem evitar o problema do uso incorreto como acima (por exemplo, uma classe base abstrata). Mas vamos deixá-los de lado por enquanto e acredite em mim que preciso desse design por algum motivo.

Então, minha pergunta é que, existe alguma maneira que eu possa impedir o usuário de escrever classe derivada incorreta como ver acima. Ou seja, se o usuário escrever uma classe de implementação derivada, digaD, mas ele derivou deBase<OtherD>, então um erro de tempo de compilação será levantado.

Uma solução é usardynamic_cast. No entanto, isso é expansivo e, mesmo quando funciona, é um erro de tempo de execução.

questionAnswers(6)

yourAnswerToTheQuestion