Eu sei que o código abaixo é uma especialização parcial de uma classe:
template <typename T1, typename T2>
class MyClass {
…
};
// partial specialization: both template parameters have same type
template <typename T>
class MyClass<T,T> {
…
};
Também sei que o C ++ não permite a especialização parcial do modelo de função (apenas é permitido o total). Mas meu código significa que especializei parcialmente meu modelo de função para argumentos de um / mesmo tipo? Porque funciona para o Microsoft Visual Studio 2010 Express! Se não, você poderia explicar o conceito de especialização parcial?
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
template <typename T1, typename T2>
inline T1 max (T1 const& a, T2 const& b)
{
return a < b ? b : a;
}
template <typename T>
inline T const& max (T const& a, T const& b)
{
return 10;
}
int main ()
{
cout << max(4,4.2) << endl;
cout << max(5,5) << endl;
int z;
cin>>z;
}
max(5,5)
resolvemax(T const&, T const&) [with T=int]
e nãomax(T1 const&, T2 const&) [with T1=int and T2=int]
?Respostas:
A especialização parcial da função ainda não é permitida de acordo com o padrão. No exemplo, você está realmente sobrecarregando e não se especializando na
max<T1,T2>
função.Sua sintaxe deveria ter olhado um pouco como abaixo, ele tinha sido autorizado:
// Partial specialization is not allowed by the spec, though! template <typename T> inline T const& max<T,T> (T const& a, T const& b) { ^^^^^ <--- [supposed] specializing here return 10; }
No caso de modelos de função, apenas a especialização completa é permitida pelo padrão C ++, - excluindo as extensões do compilador!
fonte
(T, T)
mais(T1, T2)
de(int, int)
, é porque os antigos garantias que existem 2 parâmetros e ambos os tipos são os mesmos; o último garante apenas que existem 2 parâmetros. O compilador sempre escolhe uma descrição precisa. Por exemplo, se você tivesse que fazer uma escolha entre 2 descrições de um "Rio", qual você escolheria? "coleta de água" vs "coleta de água fluindo".Já que a especialização parcial não é permitida - como outras respostas apontaram -, você pode contornar isso usando
std::is_same
estd::enable_if
, como abaixo:template <typename T, class F> inline typename std::enable_if<std::is_same<T, int>::value, void>::type typed_foo(const F& f) { std::cout << ">>> messing with ints! " << f << std::endl; } template <typename T, class F> inline typename std::enable_if<std::is_same<T, float>::value, void>::type typed_foo(const F& f) { std::cout << ">>> messing with floats! " << f << std::endl; } int main(int argc, char *argv[]) { typed_foo<int>("works"); typed_foo<float>(2); }
Resultado:
$ ./a.out >>> messing with ints! works >>> messing with floats! 2
Editar : no caso de você precisar ser capaz de tratar todos os outros casos restantes, você pode adicionar uma definição que afirma que os casos já tratados não devem corresponder - caso contrário, você cairia em definições ambíguas. A definição pode ser:
template <typename T, class F> inline typename std::enable_if<(not std::is_same<T, int>::value) and (not std::is_same<T, float>::value), void>::type typed_foo(const F& f) { std::cout << ">>> messing with unknown stuff! " << f << std::endl; } int main(int argc, char *argv[]) { typed_foo<int>("works"); typed_foo<float>(2); typed_foo<std::string>("either"); }
Que produz:
$ ./a.out >>> messing with ints! works >>> messing with floats! 2 >>> messing with unknown stuff! either
Embora essa coisa de todos os casos pareça um pouco chata, já que você tem que dizer ao compilador tudo o que você já fez, é bem possível tratar até 5 ou mais algumas especializações.
fonte
Se você realmente deseja entender os modelos, deve dar uma olhada nas linguagens funcionais. O mundo dos modelos em C ++ é uma sublinguagem puramente funcional própria.
Em linguagens funcionais, as seleções são feitas usando a correspondência de padrões :
-- An instance of Maybe is either nothing (None) or something (Just a) -- where a is any type data Maybe a = None | Just a -- declare function isJust, which takes a Maybe -- and checks whether it's None or Just isJust :: Maybe a -> Bool -- definition: two cases (_ is a wildcard) isJust None = False isJust Just _ = True
Como você pode ver, sobrecarregamos a definição de
isJust
.Bem, os modelos de classe C ++ funcionam exatamente da mesma maneira. Você fornece um principal declaração , que indica o número e a natureza dos parâmetros. Pode ser apenas uma declaração ou também atua como uma definição (sua escolha), e então você pode (se desejar) fornecer especializações do padrão e associar a elas uma versão diferente (caso contrário, seria bobo) da classe .
Para funções de modelo, a especialização é um pouco mais difícil: ela entra em conflito com a resolução de sobrecarga. Como tal, foi decidido que uma especialização estaria relacionada a uma versão não especializada e as especializações não seriam consideradas durante a resolução de sobrecarga. Portanto, o algoritmo para selecionar a função certa torna-se:
(para tratamento aprofundado, consulte GotW # 49 )
Como tal, a especialização de modelo de funções é um cidadão de segunda zona (literalmente). No que me diz respeito, estaríamos melhor sem eles: ainda não encontrei um caso em que o uso de uma especialização de modelo não pudesse ser resolvido com sobrecarga.
Não, é simplesmente uma sobrecarga e isso é bom. Na verdade, as sobrecargas geralmente funcionam como esperamos que funcionem, enquanto as especializações podem ser surpreendentes (lembre-se do artigo GotW que vinculei).
fonte
"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."
Que tal com parâmetros de modelo sem tipo?boost::mpl::integral_c<unsigned, 3u>
. Outra solução também pode ser usarenable_if
/disable_if
, embora seja uma história diferente.A especialização parcial sem classe e sem variável não é permitida, mas como disse:
Adicionar uma classe para encaminhar a chamada de função pode resolver isso. Aqui está um exemplo:
template <class Tag, class R, class... Ts> struct enable_fun_partial_spec; struct fun_tag {}; template <class R, class... Ts> constexpr R fun(Ts&&... ts) { return enable_fun_partial_spec<fun_tag, R, Ts...>::call( std::forward<Ts>(ts)...); } template <class R, class... Ts> struct enable_fun_partial_spec<fun_tag, R, Ts...> { constexpr static R call(Ts&&... ts) { return {0}; } }; template <class R, class T> struct enable_fun_partial_spec<fun_tag, R, T, T> { constexpr static R call(T, T) { return {1}; } }; template <class R> struct enable_fun_partial_spec<fun_tag, R, int, int> { constexpr static R call(int, int) { return {2}; } }; template <class R> struct enable_fun_partial_spec<fun_tag, R, int, char> { constexpr static R call(int, char) { return {3}; } }; template <class R, class T2> struct enable_fun_partial_spec<fun_tag, R, char, T2> { constexpr static R call(char, T2) { return {4}; } }; static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, ""); static_assert(fun<int>(1, 1) == 2, ""); static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, ""); static_assert(fun<char>(1, 1) == 2, ""); static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, ""); static_assert(fun<long>(1L, 1L) == 1, ""); static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, ""); static_assert(fun<double>(1L, 1L) == 1, ""); static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, ""); static_assert(fun<int>(1u, 1) == 0, ""); static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, ""); static_assert(fun<char>(1, 'c') == 3, ""); static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, ""); static_assert(fun<unsigned>('c', 1) == 4, ""); static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, ""); static_assert(fun<unsigned>(10.0, 1) == 0, ""); static_assert( std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, ""); static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, ""); static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, ""); static_assert(fun<unsigned>() == 0, "");
fonte
Não. Por exemplo, você pode se especializar legalmente
std::swap
, mas não pode definir legalmente sua própria sobrecarga. Isso significa que você não pode fazerstd::swap
trabalhar para seu próprio modelo de classe personalizado.Sobrecarga e especialização parcial podem ter o mesmo efeito em alguns casos, mas longe de todos.
fonte
swap
sobrecarga em seu namespace.Resposta tardia, mas alguns leitores atrasados podem achar útil: às vezes, uma função auxiliar - projetada de modo que possa ser especializada - pode resolver o problema também.
Então, vamos imaginar, isto é o que tentamos resolver:
template <typename R, typename X, typename Y> void function(X x, Y y) { R* r = new R(x); f(r, y); // another template function? } // for some reason, we NEED the specialization: template <typename R, typename Y> void function<R, int, Y>(int x, Y y) { // unfortunately, Wrapper has no constructor accepting int: Wrapper* w = new Wrapper(); w->setValue(x); f(w, y); }
OK, especialização de função de modelo parcial, não podemos fazer isso ... Vamos "exportar" a parte necessária para a especialização em uma função auxiliar, especialize-a e use-a:
template <typename R, typename T> R* create(T t) { return new R(t); } template <> Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal... { Wrapper* w = new Wrapper(); w->setValue(n); return w; } template <typename R, typename X, typename Y> void function(X x, Y y) { R* r = create<R>(x); f(r, y); // another template function? }
Isso pode ser interessante, especialmente se as alternativas (sobrecargas normais em vez de especializações, a solução alternativa proposta por Rubens, ... - não que essas sejam ruins ou que a minha seja melhor, apenas outra ) compartilham um monte de código comum.
fonte