Estou tentando obter um exemplo simples para entender como usar std::enable_if
. Depois de ler esta resposta , pensei que não seria muito difícil criar um exemplo simples. Eu quero usar std::enable_if
para escolher entre duas funções-membro e permitir que apenas uma delas seja usada.
Infelizmente, o seguinte não é compilado com o gcc 4.7 e, após horas e horas de tentativas, estou perguntando a vocês qual é o meu erro.
#include <utility>
#include <iostream>
template< class T >
class Y {
public:
template < typename = typename std::enable_if< true >::type >
T foo() {
return 10;
}
template < typename = typename std::enable_if< false >::type >
T foo() {
return 10;
}
};
int main() {
Y< double > y;
std::cout << y.foo() << std::endl;
}
O gcc relata os seguintes problemas:
% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x enable_if.cpp -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'
Por que o g ++ não exclui a instanciação incorreta para a segunda função de membro? De acordo com o padrão, std::enable_if< bool, T = void >::type
só existe quando o parâmetro do modelo booleano é verdadeiro. Mas por que o g ++ não considera isso como SFINAE? Eu acho que a mensagem de erro de sobrecarga vem do problema de que o g ++ não exclui a segunda função de membro e acredita que isso deve ser uma sobrecarga.
std::is_same< T, int >::value
e! std::is_same< T, int >::value
que dá o mesmo resultado.Respostas:
SFINAE só funciona se a substituição na dedução de argumento de um argumento de modelo tornar a construção mal formada. Não existe essa substituição.
Isso ocorre porque quando o modelo de classe é instanciado (o que acontece quando você cria um objeto de tipo
Y<int>
entre outros casos), ele instancia todas as suas declarações de membros (não necessariamente suas definições / corpos!). Entre eles também estão os modelos de membros. Note queT
é conhecido então e!std::is_same< T, int >::value
produz false. Por isso, criará uma classeY<int>
que contémO
std::enable_if<false>::type
acessa um tipo inexistente, para que a declaração seja mal formada. E, portanto, seu programa é inválido.Você precisa fazer com que os modelos de membros
enable_if
dependam de um parâmetro do próprio modelo de membro. Em seguida, as declarações são válidas, porque todo o tipo ainda é dependente. Quando você tenta chamar um deles, a dedução de argumento para seus argumentos de modelo acontece e SFINAE acontece como esperado. Veja esta pergunta e a resposta correspondente sobre como fazer isso.fonte
Y
classe de modelo é instanciada, o compilador não compila realmente as funções de membro do modelo; no entanto, o compilador executará a substituiçãoT
no modelo de membro DECLARATIONS para que esses modelos de membro possam ser instanciados posteriormente. Esse ponto de falha não é o SFINAE, porque o SFINAE se aplica apenas ao determinar o conjunto de funções possíveis para a resolução de sobrecarga , e instanciar uma classe não é o caso de determinar um conjunto de funções para a resolução de sobrecarga. (Ou então eu acho!)Fiz este pequeno exemplo que também funciona.
Comente se você quer que eu elabore. Eu acho que o código é mais ou menos auto-explicativo, mas, novamente, eu fiz isso para que eu possa estar errado :)
Você pode vê-lo em ação aqui .
fonte
error C4519: default template arguments are only allowed on a class template
.Q
, mesmo que seja igual aT
?test
função de membro. Ambos não podem existir ao mesmo tempo.Q
apenas encaminha o tipo de modelo de classeT
. Você pode remover o modelo de classe da seguinteT
maneira: cpp.sh/4nxw, mas isso meio que derrota o objetivo.Para quem chega atrasado e procura uma solução que "simplesmente funcione":
Ajuntar com:
A corrida oferece:
fonte
std::enable_if_t
pararesolvedType
.A partir deste post:
Mas pode-se fazer algo assim:
fonte
Uma maneira de resolver esse problema, a especialização de funções-membro é colocar a especialização em outra classe e depois herdá-la. Pode ser necessário alterar a ordem da herança para obter acesso a todos os outros dados subjacentes, mas essa técnica funciona.
A desvantagem dessa técnica é que, se você precisar testar muitas coisas diferentes para diferentes funções-membro, precisará criar uma classe para cada uma e encadear na árvore de herança. Isso é verdade para acessar membros de dados comuns.
Ex:
fonte
O booleano precisa depender do parâmetro do modelo que está sendo deduzido. Portanto, uma maneira fácil de corrigir é usar um parâmetro booleano padrão:
No entanto, isso não funcionará se você deseja sobrecarregar a função de membro. Em vez disso, é melhor usar
TICK_MEMBER_REQUIRES
da biblioteca Tick :Você também pode implementar seu próprio membro requer macro como esta (caso você não queira usar outra biblioteca):
fonte
Aqui está o meu exemplo minimalista, usando uma macro. Use colchetes duplos
enable_if((...))
ao usar expressões mais complexas.fonte