Eu tenho o seguinte problema. Eu defino um vetor dimensional N como
#include <vector>
#include <utility>
#include <string>
template <int N, typename T>
struct NVector{
typedef std::vector<typename NVector<N-1,T>::type> type;
};
template <typename T> struct NVector<1,T> {
typedef std::vector<T> type;
};
Desejo escrever um mapa de função de ordem superior que possa transformar os elementos da folha do vetor aninhado, independentemente da profundidade e retornar um novo vetor aninhado da mesma forma. eu tentei
template <int N, typename T, typename Mapper>
struct MapResult {
typedef decltype ( (std::declval<Mapper>()) (std::declval<T>()) ) basic_type;
typedef typename NVector<N, basic_type>::type vector_type;
};
template <int N, typename T, typename Mapper>
typename MapResult<N,T,Mapper>::vector_type
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
{
typename MapResult<N,T,Mapper>::vector_type out;
for(auto i = vector.begin(); i != vector.end(); i++){
out.push_back(Map(*i,mapper));
}
return out;
}
template <typename T, typename Mapper>
typename MapResult<1,T,Mapper>::vector_type
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
{
typename MapResult<1,T,Mapper>::vector_type out;
for(auto i = vector.begin(); i != vector.end(); i++){
out.push_back(mapper(*i));
}
return out;
}
e depois usá-lo como principal
int main(){
NVector<1,int>::type a = {1,2,3,4};
NVector<2,int>::type b = {{1,2},{3,4}};
NVector<1,std::string>::type as = Map(a,[](int x){return std::to_string(x);});
NVector<2,std::string>::type bs = Map(b,[](int x){return std::to_string(x);});
}
No entanto, recebo erros de compilação
<source>:48:34: error: no matching function for call to 'Map'
NVector<1,double>::type da = Map(a,[](int x)->int{return (double)x;});
^~~
<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
^
<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
^
<source>:49:34: error: no matching function for call to 'Map'
NVector<2,double>::type db = Map(b,[](int x)->int{return (double)x;});
^~~
<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
^
<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
^
2 errors generated.
Compiler returned: 1
Estou supondo que o compilador não seja inteligente o suficiente (ou o padrão não especifique como) para descobrir o parâmetro N por dedução. Existe uma maneira de conseguir isso?
Anteriormente, eu tinha esse trabalho, mas de uma maneira diferente, na verdade derivando de std :: vector, mas não gosto dessa solução, pois seria bom trabalhar com o código existente no momento sem precisar introduzir um novo tipo de invólucro.
/// define recursive case
template <int N, typename T>
struct NVector : std::vector<NVector<N-1,T>>;
/// define termination case
template <typename T>
struct NVector<1, T> : public std::vector<T>;
código ao vivo em https://godbolt.org/z/AMxpuj
fonte
T
como argumento e usarenable_if
Respostas:
Você não pode deduzir de um typedef - especialmente um typedef declarado em uma classe auxiliar - porque não há como o compilador executar o mapeamento reverso de um tipo para combinações de argumentos.
(Considere que, no caso geral, isso é impossível, pois alguém pode se especializar
struct NVector<100, float> { using type = std::vector<char>; };
e o compilador não tem como saber se isso é pretendido.)Para ajudar o compilador, você pode definir o mapeamento reverso:
Possível uso (C ++ 17, mas é fácil de traduzir para dialetos arcaicos):
fonte
N==0
e para outrosComo já foi apontado em outras respostas, o problema aqui é que o especificador de nome aninhado em um ID qualificado é um contexto não deduzido [temp.deduct.type] /5.1 . Outras respostas também já apresentaram várias maneiras diferentes de fazer sua abordagem original funcionar. Gostaria de dar um passo atrás e considerar o que realmente você deseja fazer.
Todos os seus problemas decorrem do fato de você estar tentando trabalhar em termos do modelo auxiliar
NVector
. O único objetivo deste modelo auxiliar parece ser calcular uma especialização de aninhadostd::vector
. O único objetivo do modelo auxiliarMapResult
parece ser calcular a especialização de aninhadostd::vector
que seria necessário para capturar o resultado da aplicação de suamapper
função arbitrária a cada elemento da estrutura do vetor de entrada aninhado. Nada obriga a expressar seuMap
modelo de função em termos desses modelos auxiliares. De fato, a vida é muito mais simples se nos livrarmos deles. Tudo o que você realmente queria fazer era aplicar umamapper
função arbitrária a cada elemento de umastd::vector
estrutura aninhada . Então, vamos fazer isso:exemplo de trabalho aqui
Basta soltar os tipos de retorno à direita se você puder usar o C ++ 14 ou mais recente.
Se o que você realmente deseja fazer é apenas armazenar e trabalhar em uma matriz n D, considere que uma estrutura de aninhado
std::vector
não é necessariamente a maneira mais eficiente de fazer isso. A menos que você precise que cada subvetor tenha um tamanho potencialmente diferente, não há razão para aumentar exponencialmente o número de alocações de memória dinâmica que você executa com o número de dimensões e seguir o caminho de cada elemento. Basta usar umstd::vector
para conter todos os elementos da matriz n D e definir um mapeamento entre os índices lógicos dos elementos n D e o índice de armazenamento linear 1D, por exemplo, de maneira semelhante ao sugerido nesta resposta. Fazer isso não será apenas mais eficiente do que os vetores de aninhamento, mas também permite alterar facilmente o layout da memória em que seus dados são armazenados. Além disso, como o armazenamento subjacente é uma matriz linear simples, a iteração sobre todos os elementos pode ser feita usando apenas um loop simples e a resposta à sua pergunta sobre o mapeamento de um intervalo de elementos para outro seria simplesmentestd::transform
…fonte
std::vectors
. A abordagem acima faz exatamente isso e funciona para qualquer N !? Existem duas sobrecargas, uma correspondendo ao caso de um vetor que contém outro vetor e levando à recursão em um nível inferior, e outra tratando o caso base em que a recursão para…Você não precisa
NVector
definirMapResult
eMap
.fonte
Em geral
typename NVector<N,T>::type
, não permite deduzirN,T
porque pode haver muitas instâncias de um modelo que produzem o mesmo tipo aninhado.Sei que você escreveu um mapeamento 1: 1, mas o idioma não exige, e, portanto, não há suporte para trabalhar com versões anteriores dessa maneira. Afinal, você escreveu
typename NVector<N,T>::type
, mas o que você está realmente passando éstd::vector<std::vector<int>>
ou o que quer. Não existe uma maneira geral de recuperá-lo.A solução simples é usar o NVector como um tipo de valor, e não apenas como uma maneira de produzir typedefs de vetor.
altere Map e MapResult para trabalhar diretamente em termos de
NVector<N,T>
, o que permite a dedução de tipo, como de costume. Por exemplo, o mapa geral se tornaFinalmente, você precisa declarar suas variáveis locais como
NVector<1,int>
não::type
, e infelizmente os inicializadores se tornam um pouco mais feios, pois você precisa envolver um extra{}
em cada nível. Você sempre pode escrever um construtor paraNVector
contornar isso, no entanto.Ah, e considere usar em
std::transform
vez de escrever esse loop manualmente.fonte
T
ser do tipostd::vector
.Você pode usar uma especialização parcial para deduzir N para trás, por assim dizer.
Isso poderia ser usado com o SFINAE para fazer algo como
fonte
É perfeitamente correto que o compilador não tente adivinhar o que você quer dizer, porque é ambíguo. Deseja chamar a função com
NVector<2, int>
ouNVector<1, std::vector<int>>
? Ambos são totalmente válidos e dariam a você o mesmotype
membro typedef.Sua solução anterior funcionou, já que você provavelmente passou o vetor nesse tipo (então o argumento era do tipo
NVector<2, int>
e a partir daí é fácil deduzir os parâmetros corretos do modelo). Você tem três possibilidades na minha opinião:std::vector
novo em seu tipo personalizado. Mas eu faria isso não com herança, mas apenas com um membro e conversão implícita no tipo desse membro.Nvector<N,T>
faria) que desambigua a chamada.Eu acho que o terceiro é o mais fácil e claro.
fonte
T
eN
não são dedutíveis em:Em vez disso, você pode fazer:
Demo
fonte