Especialização parcial do template de função C ++?

87

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;
}
Narek
fonte
Procure essa analogia de especialização de classe. Se isso é chamado de especialização de classe, por que eu deveria considerar a mesma coisa para função como sobrecarga ??
Narek
1
Não, a sintaxe de especialização é diferente. Observe a (suposta) sintaxe de especialização de função na minha resposta abaixo.
iammilind
2
Por que isso não gera um erro "Call to max is ambigious"? Como max(5,5)resolve max(T const&, T const&) [with T=int]e não max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Respostas:

81

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!

iammilind
fonte
1
@Narek, A especialização de função parcial não faz parte do padrão (por quaisquer motivos). Acho que o MSVC o suporta como uma extensão. Pode ser depois de algum tempo, seria permitido por outros compiladores também.
iammilind
1
@iammilind: Sem problemas. Ele já parece saber disso. É por isso que ele está tentando isso para o template de função também. Então eu editei novamente, deixando claro agora.
Nawaz
19
Alguém que possa explicar por que a especialização parcial não é permitida?
HelloGoodbye
2
@NHDaly, não dá erro de ambigüidade porque uma função corresponde melhor do que a outra. Por que ele seleciona (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".
iammilind
1
@kfsone, acho que esse recurso está em revisão, portanto, aberto para interpretação. Você pode consultar esta seção open-std , que vi em Por que o padrão C ++ não permite especialização parcial do template de função?
iammilind
43

Já que a especialização parcial não é permitida - como outras respostas apontaram -, você pode contornar isso usando std::is_samee std::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.

Rubens
fonte
Realmente não há necessidade de fazer isso, pois isso pode ser tratado por sobrecarga de função de uma maneira muito mais simples e clara.
Adrian
2
@Adrian Eu realmente não consigo pensar em nenhuma outra abordagem de sobrecarga de função para resolver isso. Você notou que a sobrecarga parcial não é permitida, certo? Compartilhe conosco sua solução, se você acha que é mais clara.
Rubens
1
Existe alguma outra maneira de capturar facilmente todas as funções do modelo?
Nick
15

O que é especialização?

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:

  1. Execute a resolução de sobrecarga, entre funções regulares e modelos não especializados
  2. Se um modelo não especializado for selecionado, verifique se existe uma especialização para ele que seria uma correspondência melhor

(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.

Isso é uma especialização de modelo?

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).

Matthieu M.
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?
Jules GM
@Julius: você ainda pode usar a sobrecarga, embora introduzindo um parâmetro fictício como boost::mpl::integral_c<unsigned, 3u>. Outra solução também pode ser usar enable_if/ disable_if, embora seja uma história diferente.
Matthieu M.
7

A especialização parcial sem classe e sem variável não é permitida, mas como disse:

Todos os problemas da ciência da computação podem ser resolvidos por outro nível de indireção. —— David Wheeler

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, "");
user2709407
fonte
4

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.

Cachorro
fonte
4
É por isso que você coloca sua swapsobrecarga em seu namespace.
jpalecek
2

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.

Aconcagua
fonte