Currying para modelos em metaprogramação em C ++

Esta é mais uma questão conceitual. Eu estou tentando encontrar a maneira mais fácil de converter um modelo de dois arg (os argumentos sendo tipos) em um modelo de um arg. Ou seja, ligando um dos tipos.

Este seria o equivalente da meta-programação do bind no boost / std. Meu exemplo inclui um possível caso de uso, que é, passando std::is_same como argumento de modelo para um modelo que usa um argumento de modelo de modelo de um argumento ( std::is_same sendo um modelo de dois argumentos), ie para TypeList::FindIf . O TypeList não é totalmente implementado aqui, nem é FindIf , mas você começa a idéia. É preciso um “predicado unário” e retorna o tipo para o qual esse predicado é verdadeiro ou void se não for esse tipo.

Eu tenho 2 variantes de trabalho, mas o primeiro não é um one-liner e o segundo usa uma engenhoca BindFirst bastante detalhada, que não funcionaria para argumentos de modelo não-tipo. Existe uma maneira simples de escrever um one-liner? Eu acredito que o procedimento que estou procurando é chamado de currying .

 #include  template<template class Function, typename FirstArg> struct BindFirst { template using Result = Function; }; //template using IsInt = BindFirst::Result; template using IsInt = std::is_same; struct TypeList { template<template class Predicate> struct FindIf { // this needs to be implemented, return void for now typedef void Result; }; }; int main() { static_assert(IsInt::value, ""); static_assert(!IsInt::value, ""); // variant #1: using the predefined parameterized type alias as predicate typedef TypeList::FindIf::Result Result1; // variant #2: one-liner, using BindFirst and std::is_same directly typedef TypeList::FindIf< BindFirst::Result>::Result Result2; // variant #3: one-liner, using currying? //typedef TypeList::FindIf<std::is_same>::Result Result2; return 0; } 

Clique aqui para o código no compilador online GodBolt.

Eu acho que a maneira típica de fazer isso é manter tudo no mundo dos tipos. Não leve modelos de modelos – eles são confusos. Vamos escrever uma metafunction chamada ApplyAnInt que terá uma “metafunction class” e aplicar int a ela:

 template  struct ApplyAnInt { using type = typename Func::template apply; }; 

Onde uma class de metafuncional simples pode estar apenas verificando se o tipo dado é um int :

 struct IsInt { template  using apply = std::is_same; }; static_assert(ApplyAnInt::type::value, ""); 

Agora o objective é apoiar:

 static_assert(ApplyAnInt>::type::value, ""); 

Nós podemos fazer isso. Vamos chamar tipos que contenham _ “expressões lambda” e gravar uma metafunction chamada lambda que encaminhará uma class de metafunction que não é uma expressão lambda ou produzirá uma nova metafunction se:

 template  struct lambda { using type = T; }; template  struct lambda::value>> { struct type { template  using apply = typename apply_lambda::type; }; }; template  using lambda_t = typename lambda::type; 

Então, atualizamos nossa metafunction original:

 template  struct ApplyAnInt { using type = typename lambda_t::template apply; }; 

Agora, isso deixa duas coisas: precisamos de is_lambda_expr e apply_lambda . Aqueles realmente não são tão ruins assim. Para o primeiro, veremos se é uma instanciação de um modelo de class no qual um dos tipos é _ :

 template  struct is_lambda_expr : std::false_type { }; template  

E para apply_lambda , apenas replaceemos o _ pelo tipo dado:

 template  struct apply_lambda; template  

E isso é tudo que você precisa, na verdade. Eu vou deixar isso para suportar arg_ como um exercício para o leitor.

Sim, eu tive esse problema para. Demorou algumas iterações para descobrir uma maneira decente de fazer isso. Basicamente, para fazer isso, precisamos especificar uma representação razoável do que queremos e precisamos. Eu peguei emprestado alguns aspectos de std::bind() em que eu quero especificar o template que eu gostaria de vincular e os parâmetros que eu quero vincular a ele. Então, dentro desse tipo, deve haver um modelo que permitirá que você passe um conjunto de tipos.

Então nossa interface ficará assim:

 template  

Agora nossa implementação terá esses parâmetros mais um container de tipos que serão aplicados no final:

 template  

Nosso caso base nos fornecerá um tipo de modelo, que eu chamarei de ttype , que retornará um modelo dos tipos contidos:

 template  

Então temos o caso de mover o próximo tipo para o container e ter ttype referir ao tipo ttype no caso base um pouco mais simples:

 template  

E, finalmente, precisamos de um remapeamento dos modelos que serão passados ​​para ttype :

 template  

Agora, como os programadores são preguiçosos e não querem digitar std::integral_constant para cada parâmetro a ser remapeado, especificamos alguns aliases:

 using t0 = std::integral_constant; using t1 = std::integral_constant; using t2 = std::integral_constant; ... 

Ah, quase esqueci a implementação da nossa interface:

 template  

Observe que tbind_impl foi colocado em um namespace de detail .

E voila, tbind !

Infelizmente, há um defeito antes do c ++ 17. Se você passar tbind::ttype para um modelo que espera um template com um determinado número de parâmetros, você receberá um erro, pois o número de parâmetros não corresponde (o número específico não corresponde a nenhum número). Isso complica as coisas, exigindo um nível adicional de indireção. 🙁

 template  

Usar isso para encapsular o tbind forçará o compilador a reconhecer o modelo com o número especificado de parâmetros.

Exemplo de uso:

 static_assert(!tbind::ttype::value, "failed"); static_assert( tbind::ttype::value, "failed"); static_assert(!any_to_specific< tbind::ttype , 1 >::ttype::value, "failed"); static_assert( any_to_specific< tbind::ttype , 1 >::ttype::value, "failed"); 

Tudo isso é bem sucedido.