este não é um bug específico, mas uma questão geral de design em C++. Considere o seguinte cenário:
struct MyInterface {};
struct Data : MyInterface{};
struct DataWrapper
{
SomeContainerOf<MyInterface*> getData()
{...}
private:
std::vector<Data> dataStorage;
};
Em termos dos meus requisitos, preciso da função de acesso para DataWrapper
retornar um contêiner de MyInterface*
valores (ponteiros porque estamos realmente retornando Data
valores).
Idealmente, queremos fazer isso sem fazer uma cópia. Quero saber a melhor maneira de fazer isso. Pelo que posso dizer, algo como o seguinte é a maneira idiomática do C++ de fazer isso:
auto DataWrapper::getData()
{
return dataStorage
| std::ranges::views::transform(
[](Data& d) -> MyInterface* { return &d; }
);
}
Mas o problema é que está muito claro que esse método acessador é explícito sobre seus valores de retorno - preciso que saibam que esse método retorna essa lista, e estou preocupado que ao fazer o tipo de retorno auto
, isso atrapalhe um pouco isso . Parece que (posso estar errado aqui) você não deveria saber qual view
é o tipo de a (em um estilo semelhante aos lambdas)
A alternativa que estou considerando seria fazer uma visualização customizada (onde eu poderia deixar o tipo um pouco mais claro), mas realmente me dói reimplementar algo que está tão claramente no padrão. Qual seria a medida recomendada para tal cenário? O C++ moderno diz que devo manter o valor de retorno auto
?
Se você está preocupado com o fato de isso
auto
estar muito implícito na expressão do tipo de retorno, usar restritoauto
pode ser uma escolha apropriada. Por exemploFarei várias recomendações semi-ortogonais:
Recomendação 1: Esclareça o tipo de retorno com uma definição de tipo
Então, faça isso! Em vez de declarar a função como retornando automaticamente:
detail_
namespace, etc. Agora você pode usá-lo, ou seu tipo, sem repeti-lo. Suponha que você o tenha definido como chamadofoo()
bar
pelo tipo pelo qual deseja que os usuários deste método conheçam o resultado.Nota: a resposta de 康桓瑋sugere o uso de um restrito
auto
, para um efeito semelhante. É uma boa ideia, possivelmente ainda melhor que esta (embora um pouco mais detalhada). Então, por favor, vote a favor da resposta deles.Recomendação 2: Considere cuidadosamente a necessidade de herança
Antes mesmo de considerar a
DataWrapper
classe "maior", mantendo o vetor - talvez você deva pensar se realmente precisa usarMyInterface
em vez deData
. Você parece estar confiando, até certo ponto, no retornoData
degetData()
- daí o nome.MyInterface
? Caso contrário, considere abandoná-lo (ou mantê-lo, mas não usá-lo, a não ser para simplificar a maneira como você defineData
).MyInterface
? Ou eles não são conhecidos no momento da definição deMyInterface
? Se a resposta for "não", considere usar anstd::variant<Data,C1,C2,C3>
para o caso deC1
,C2
,C3
serem os herdeiros deMyInterface
.Se você puder fazer qualquer um desses, é possível evitar a necessidade de uma
DataWrapper
classe _completamente e simplesmente ter umstd::vector<foo>
para algum foo relevante (Data
, ou a variante, classe ou mesmostd::any
).Não estou dizendo que evitar herança seja “melhor”; significa mudar de uma compensação de benefícios e prejuízos para outra. Mas muitas pessoas simplesmente optam pela herança porque é a maneira mais tradicional de fazer as coisas em C++, em vez de ser a que melhor atende às suas necessidades.
Recomendação 3: Não devolva um container (ou intervalo), seja o container!
Você tem uma classe do tipo valor (
DataWrapper
) com um vetor de dados. Agora, você não está disposto a permitir que as pessoas usem esse vetor diretamente, portanto,DataWrapper
não pode ser usado como um contêiner C++ deData
s.Bem - explore isso! Torne-o utilizável como um contêiner de objetos herdados
MyInheritance
, ou seja,MyInheritance
referências! Implemente o início, o cbegin, o tamanho, o vazio, o iterador e todas as outras partes desse requisito nomeado. Talvez vá tão longe quanto implementar tudo em ContiguousContainer ... embora isso torne sua classe um pouco mais complexa, você estará na verdade economizando a complexidade indireta do usostd::ranges
(muito código subjacente lá), além de - seus usuários não precisa saber sobregetData()
, então, em termos de uso, você provavelmente está simplificando as coisas.Como a lista de captura do lambda está vazia, o construtor padrão pode instanciá-la. Portanto, podemos declarar detalhadamente o tipo de retorno. Mas como os construtores do adaptador de intervalo são explícitos, ocorre alguma repetição de código:
Usei
ref_view
no snippet porque não consegui verificar se o primeiro argumento de tipotransform_view
deveria ser um contêiner ou uma referência a um contêiner. Masref_view
satisfaz as restrições e incorpora uma referência ao contêiner.