Eu costumo participar de maratonas de programação. Algo muito comum é precisar ordenar um vetor de valores relacionados. Para isso eu costumo utilizar pair, o problema é quando é preciso mais de um parâmetro, nesse caso é costume criar um pair com o segundo elemento sendo também um pair. Esse processo, além de ser feio, não escala. As tuplas foram inseridas no C++11 para generalizar o conceito de pares.

Uma tupla é simplesmente um conjunto ordenado de elementos que podem ser de tipos diferente. Abaixo está um bloco de código mostrando como utilizar tuplas. Na linha 1, uma declaração de tuplas usando initializer lists para inicializá-la. Na linha 2 pode-se ver como acessar os valores de uma tupla. Em tuplas não temos os métodos nomeados first e second como em pairs, mas sim uma função global que acessa os dados internos de uma tupla. É importante lembrar que, como arrays, as tuplas são indexadas a partir de zero, então para obter o segundo elemento usamos o 1 como parâmetro do template, como mostrado na linha 2.

1
2
std::tuple<bool, double> tuple {true, 2.0};
std::cout << std::get<1>(tuple) << std::endl; // "2"

Agora imagine que nós queremos atribuir cada valor de um pair a uma variável (como em Python por exemplo). Isso pode ser feito com a função tie como mostrado abaixo.

1
std::tie(std::ignore, sqrt_result) = real_sqrt(2.0);

Outra função da biblioteca padrão é tuple_cat que concatena os valores de várias tuplas em uma única tupla como mostrado a seguir.

1
std::tuple_cat(std::make_tuple(1,2), std::make_tuple(1,2,3));

Um uso interessante para tuplas é o de retornar múltiplos valores de uma função. Por exemplo, vamos dizer que queremos fazer uma função que pode falhar. Podemos retornar uma tupla em que o primeiro valor é um booleano indicando se a função teve sucesso e o segundo elemento é o valor de retorno. Como exemplo aqui vamos usar a função raiz quadrada da biblioteca padrão.

No código temos a função que retorna o primeiro valor falso se o parâmetro passado é negativo. Caso contrário retorna uma tupla com o primeiro valor verdadeiro e o segundo com o resultado. Note na linha 8, estamos contando com a otimização do compilador de retorno de um objeto: de fato apenas uma tupla vai ser criada aqui sem que precisemos fazer nada.

1
2
3
4
5
6
7
8
9
10
11
12
std::tuple<bool, double> real_sqrt(double x) \{
if (x < 0)
return std::make_tuple(false, -1);
else
return std::make_tuple(true, sqrt(x));
\}
// ...
std::tuple<bool, double> result = real_sqrt(-2.0);
if (std::get<0>(result))
std::cout << std::get<1>(result) << std::endl;
else
std::cout << "negative value fed" << std::endl; // will print this

Esse tipo de retorno pode ser generalizado com um tipo, o código abaixo só é suportado pelas versões mais novas do gcc.

1
2
3
4
  template<typename T>
using function_result = std::tuple<bool, T>;
// ...
function_result<double>&& result2 = real_sqrt2(-2);

Diferença entre tuplas e structs

Mas então tu te perguntas qual a diferença então entre tuplas e structs. Primeiramente o leiaute das tuplas não necessariamente é ordenado, o compilador é livre para implementá-las como quiser. Outra diferença é em relação aos tipos, todas tuplas com o mesmo número de argumentos e com os mesmos tipos são comparáveis. A comparação é feita lexicograficamente (se o primeiro valor de uma tupla é menor que o primeiro valor da segunda, a primeira tupla é menor, se forem iguais o resto é comparado).

Assim tuplas são bem utilizadas quando não existe uma grande relação semântica em algum agrupamento de variáveis, principalmente se precisamos retornar ou ordenar um grupo de variáveis.

Extra