Murilo :P

C++, Computação, Programação, Web e afins :)

Posts Tagged ‘SFINAE

C++ Idioms: SFINAE

leave a comment »

Introdução

Nesse post falarei sobre uma técnica (ou recurso) utilizada em  C++ que estou utilizando muito no meu projeto[0][1] na Boost Libraries[2] no GSoC, essa técnica é o SFINAE.

SFINAE[3] é uma sigla para “Substitution failure is not an error” que em uma tradução livre quer dizer “Falha na substituição não é um erro”. Segundo[3]:

SFINAE se refere a uma situação em C++ que uma substituição inválida de templates não é um erro.

Especificamente, na criação de um conjunto candidato para a resolução de sobrecarga, alguns (talvez todos) os candidatos deste conjunto pode ser resultante da substituição de argumentos templates deduzidos para os parâmetros de template. Se um erro ocorre durante a substituição, o compilador remove essa potencial sobrecarga do conjunto candidato ao invés de parar com um erro de compilação. Se restam um ou mais candidatos (i.é: pelo menos uma sobrecarga é válida), a resolução é feita e a invocação é bem formada.

Como funciona

Para entender melhor vejamos esse trecho de código:

template <typename T>
struct A
{
	typedef T type;
	T value;
};

template <>
struct A<bool>
{
};

template<typename T>
typename A<T>::type func(const A<T>& param)
{
	std::cout << param.value << std::endl;
}

Esse código compila sem warnings. A função template func() é sobrecarregada e retorna o tipo T. Mas você deve estar se perguntando, o que acontece quando T = bool? Esse é justo o ponto do SFINAE!
Para T = bool, typename A::type seria uma declaração inválida já que A não contém o tipo type declarado, porém, com SFINAE, essa sobrecarga é simplesmente descartada e para qualquer outro T que não bool, func(A) é uma chamada legal porque sua sobrecarga é gerada.

Aplicações

Como eu disse no começo deste post eu estou utilizando bastante SFINAE no meu projeto do Google Summer of Code com uma utilidade chamada enable_if[4] encontrado na Boost.

Enable_if é uma classe (ou struct) que “ativa” a declaração de algo (classe, função, etc) se uma condição for satisfeita por meio do SFINAE. Vou mostrar aqui uma maneira de fazer um enable_if like, e quem quiser saber mais sobre o enable_if da Boost é encorajado a ler[4][5].

Vamos ao código:

/* Cond é a condição de ativação
 * Type é o tipo que será declarado pelo membro type.
 */
template <bool Cond, typename Type = void>
struct enable_if_c
{
	typedef Type type;
};

/*
 * Especialização, quando Cond for false,
 * 	esse template vazio é invocado.
 */
template <typename Type>
struct enable_if_c<false, Type>
{
	// empty
};

Ou seja, sempre quando Cond for true, existirá um membro type declarado em enable_if_c, quando Cond for false o membro type não existirá.

Um exemplo de uso:

/*
 * Nossa função pega o bit mais significativo de value e retorna um bool.
 * 	***Note que nesse exemplo estamos assumindo que T é um tipo inteiro.***
 */

/*
 * Para retornar o bit mais significativo de um T com 4bytes (32 bits),
 * 	deslocamos 31 bits para a direita e fazemos um & com 1.
 */
template <typename T>
typename enable_if_c<sizeof(T) == 4, bool>::type msb(T value)
{
	std::cout << "Value have 4 bytes.\n";
	return (value >> 31) & T(1);
}

/*
 * Porém se T é um tipo de 1byte (8 bits) o deslocamento deve ser de
 * 	7 bits para pegarmos o bit mais significativo.
 */
template <typename T>
typename enable_if_c<sizeof(T) == 1, bool>::type msb(T value)
{
	std::cout << "Value have 1 byte.\n";
	return (value >> 7) & T(1);
}

Ou seja, a função de cima, é sobrecarregada para todos os tipos que tenham 32 bits (sizeof(T) == 4) e a de baixo é sobrecarregada para todos os tipos que tenham 8 bits (sizeof(T) == 1). Assim, no processo de resolução de sobrecarga, os outros tipos que não tem nem 8 nem 32 bits, são descartadas pois cairá na especialização enable_if_c que não possui um membro type (usado como retorno da função msb), comportamento citado anteriormente.

Para testar:

int main()
{
	int a = -3;
	unsigned b = 190000;
	char c = 129;
	uint16_t d = 10;
	uint64_t e = 10000000;
	
	std::cout << "Retorno int: " << msb(a) << std::endl;
	std::cout << "Retorno unsigned: " << msb(b) << std::endl;
	std::cout << "Retorno char: " << msb(c) << std::endl;
}

Se tentarmos executar com uma condição que não é true, teremos um erro em tempo de compilação, como podemos ver abaixo:

...
uint16_t d = 10;
uint64_t e = 10000000;

std::cout << "Ret: uint16_t: " << msb(d) << std::endl
std::cout << "Ret: uint64_t: " << msb(e) << std::endl;
...

/*
/Users/murilo/Documents/programming/blog/sfinae.cpp: In function ‘int main()’: /Users/murilo/Documents/programming/blog/sfinae.cpp:61: error: no matching function for call to ‘msb(uint16_t&)’ 
/Users/murilo/Documents/programming/blog/sfinae.cpp:62: error: no matching function for call to ‘msb(uint64_t&)’
*/

Alguns outros exemplos usando o enable_if da Boost você pode encontrar no meu projeto[1] na Boost onde utilizo enable_if aos montes.

Nesse post exemplifiquei SFINAE com funções, mas também é aplicável para classes. Um exemplo você pode ver aqui [6].

Até a próxima!

Referências

[0] – Estou no Google Summer of Code 2010https://murilo.wordpress.com/2010/05/03/estou-no-google-summer-of-code-2010
[1] – Source of Bits and Ints projecthttps://svn.boost.org/trac/boost/browser/sandbox/SOC/2010/bits_and_ints/boost/integer
[2] – Boost C++ Librarieshttp://www.boost.org/
[3] – Artigo “Substitution failure is not an error” na Wikipediahttp://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error
[4] – Boost enable_ifhttp://www.boost.org/doc/libs/1_43_0/libs/utility/enable_if.html
[5] – enable_if.hpphttp://www.boost.org/doc/libs/1_35_0/boost/utility/enable_if.hpp
[6] – static_sign_extend.hpp at Boost Trac – https://svn.boost.org/trac/boost/browser/sandbox/SOC/2010/bits_and_ints/boost/integer/static_sign_extend.hpp

Written by Murilo Adriano

19 de June de 2010 at 17:02

Posted in Boost, C/C++, Programação

Tagged with , , , ,