Como obter o endereço de uma função lambda C ++ dentro da própria lambda?

53

Estou tentando descobrir como obter o endereço de uma função lambda dentro de si. Aqui está um código de exemplo:

[]() {
    std::cout << "Address of this lambda function is => " << ????
}();

Eu sei que posso capturar o lambda em uma variável e imprimir o endereço, mas quero fazê-lo no lugar quando esta função anônima estiver em execução.

Existe uma maneira mais simples de fazer isso?

Daksh
fonte
24
Isso é apenas por curiosidade ou há um problema subjacente que você precisa resolver? Se houver um problema subjacente, pergunte diretamente sobre isso, em vez de perguntar sobre uma única solução possível para um problema (para nós) desconhecido.
Algum programador
41
... confirmando efetivamente o problema XY.
Ildjarn 06/11/19
8
Você pode substituir o lambda por uma classe functor escrita manualmente e, em seguida, usar this.
9139 HolyBlackCat
28
"Obter o endereço de uma função lamba dentro de si" é a solução , uma solução na qual você se concentra por pouco. Pode haver outras soluções, que podem ser melhores. Mas não podemos ajudá-lo com isso, pois não sabemos qual é o verdadeiro problema. Nem sabemos para que você usará o endereço. Tudo o que estou tentando fazer é ajudá-lo com seu problema real.
Algum programador cara,
8
@Someprogrammerdude Enquanto a maior parte do que você está dizendo é sensata, não vejo problema em perguntar "Como o X pode ser feito?". X aqui está "obtendo o endereço de um lambda de dentro de si". Não importa que você não saiba para que endereço será usado e não importa que haja soluções "melhores", na opinião de outra pessoa que pode ou não ser viável em uma base de código desconhecida ( para nós). Uma idéia melhor é simplesmente se concentrar no problema declarado. Isso é factível ou não é. Se for, então como ? Caso contrário, mencione que não é e algo mais pode ser sugerido, IMHO.
Codigo_dredd 7/11/19

Respostas:

32

Não é diretamente possível.

No entanto, capturas lambda são classes e o endereço de um objeto coincide com o endereço de seu primeiro membro. Portanto, se você capturar um objeto por valor como a primeira captura, o endereço da primeira captura corresponde ao endereço do objeto lambda:

int main() {
    int i = 0;
    auto f = [i]() { printf("%p\n", &i); };
    f();
    printf("%p\n", &f);
}

Saídas:

0x7ffe8b80d820
0x7ffe8b80d820

Como alternativa, você pode criar um padrão de design decorador lambda que transmita a referência para a captura lambda em seu operador de chamada:

template<class F>
auto decorate(F f) {
    return [f](auto&&... args) mutable {
        f(f, std::forward<decltype(args)>(args)...);
    };
}

int main() {
    auto f = decorate([](auto& that) { printf("%p\n", &that); });
    f();
}
Maxim Egorushkin
fonte
15
"o endereço de um objeto coincide com o endereço de seu primeiro membro" É especificado em algum lugar que as capturas estão ordenadas ou que não há membros invisíveis?
n. 'pronomes' m.
35
@ n.'pronouns'm. Não, esta é uma solução não portátil. Uma implementação de captura pode potencialmente ordenar os membros do maior para o menor para minimizar o preenchimento, o padrão permite isso explicitamente.
Maxim Egorushkin
14
Re, "esta é uma solução não portátil". Esse é outro nome para comportamento indefinido.
Solomon Slow
11
@ruohola Difícil de dizer. O "endereço de um objeto coincide com o endereço de seu primeiro membro" é verdadeiro para os tipos de layout padrão . Se você testou se o tipo de lambda era de layout padrão sem chamar o UB, você poderia fazer isso sem incorrer no UB. O código resultante teria um comportamento dependente da implementação. Simplesmente fazer o truque sem primeiro testar sua legalidade é, no entanto, UB.
Ben Voigt
4
Acredito que não seja especificado , conforme § 8.1.5.2, 15: Quando a expressão lambda é avaliada, as entidades capturadas por cópia são usadas para inicializar diretamente cada membro de dados não estático correspondente do objeto de fechamento resultante e os membros de dados não estáticos correspondentes às capturas init são inicializados conforme indicado pelo inicializador correspondente (...). (Para membros da matriz, os elementos da matriz são inicializados diretamente em ordem crescente de subscrito.) Essas inicializações são executadas na ordem ( não especificada ) na qual os membros de dados não estáticos são declarados.
Erbureth diz Reinstate Monica 07/11/19
51

Não há como obter diretamente o endereço de um objeto lambda dentro de um lambda.

Agora, como acontece, isso geralmente é útil. O uso mais comum é para se recuperar.

Ele y_combinatorvem de idiomas em que você não poderia falar sobre si mesmo até onde foi definido. Ele pode ser implementado facilmente no :

template<class F>
struct y_combinator {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args) const {
    return f( f, std::forward<Args>(args)... );
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args) {
    return f( f, std::forward<Args>(args)... );
  }
};

agora você pode fazer isso:

y_combinator{ [](auto& self) {
  std::cout<<"Address of this lambda function is => "<< &self;
} }();

Uma variação disso pode incluir:

template<class F>
struct y_combinator {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args) const {
    return f( *this, std::forward<Args>(args)... );
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args) {
    return f( *this, std::forward<Args>(args)... );
  }
};

onde o selfpassado pode ser chamado sem passar selfcomo o primeiro argumento.

O segundo corresponde ao combinador real y (também conhecido como combinador de ponto fixo), acredito. O que você deseja depende do que você quer dizer com 'endereço de lambda'.

Yakk - Adam Nevraumont
fonte
3
uau, os combinadores Y são bastante difíceis de entender em linguagens de tipo dinâmico como Lisp / Javascript / Python. Eu nunca pensei que veria um em C ++.
Jason S
13
Eu me sinto como se você fizer isso em C ++ que você merece ser preso
user541686
3
@MSalters Incerto. Se o Flayout não é padrão, y_combinatornão é, portanto as garantias sensatas não são fornecidas.
Yakk - Adam Nevraumont 7/11
2
@carto a resposta principal só funciona se o lambda estiver dentro do escopo e você não se importar em apagar a sobrecarga. A terceira resposta é o combinador y. A segunda resposta é um ycombinator manual.
Yakk - Adam Nevraumont 7/11
2
Recurso @kaz C ++ 17. Em 14/11, você escreveria uma função make, que deduziria F; em 17 você pode deduzir com nomes de modelos (e às vezes guias dedução)
Yakk - Adam Nevraumont
25

Uma maneira de resolver isso seria substituir o lambda por uma classe functor escrita à mão. É também o que o lambda está essencialmente sob o capô.

Então você pode obter o endereço this, mesmo sem nunca atribuir o functor a uma variável:

#include <iostream>

class Functor
{
public:
    void operator()() {
        std::cout << "Address of this functor is => " << this;
    }
};

int main()
{
    Functor()();
    return 0;
}

Resultado:

Address of this functor is => 0x7ffd4cd3a4df

Isso tem a vantagem de ser 100% portátil e extremamente fácil de raciocinar e entender.

ruohola
fonte
9
O functor pode até ser declarado como um lambda:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Erroneous
-1

Capture a lambda:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}
Vincent Fourmond
fonte
11
A flexibilidade de a std::functionnão é necessária aqui, porém, e tem um custo considerável. Além disso, copiar / mover esse objeto irá quebrá-lo.
Deduplicator
@ Reduplicator, por que não é necessário, pois essa é a única resposta que é compatível com o padrão? Por favor, forneça uma resposta que funcione e não precise da função std ::.
Vincent Fourmond
Essa parece ser uma solução melhor e mais clara, a menos que o único ponto seja obter o endereço da lambda (que por si só não faz muito sentido). Um caso de uso comum seria ter acesso ao lambla dentro de si, para fins de recursão, por exemplo, consulte: stackoverflow.com/questions/2067988/… em que a opção declarativa como função foi amplamente aceita como solução :)
Abs
-6

É possível, mas depende muito da otimização da plataforma e do compilador.

Na maioria das arquiteturas que conheço, existe um registro chamado ponteiro de instruções. O objetivo desta solução é extraí-la quando estivermos dentro da função.

Em amd64, o código a seguir deve fornecer endereços próximos à função

#include <iostream>

void* foo() {
    void* n;
    asm volatile("lea 0(%%rip), %%rax"
      : "=a" (n));
    return n;
}

auto boo = [](){
    void* n;
    asm volatile("lea 0(%%rip), %%rax"
       : "=a" (n));
    return n;
};

int main() {
    std::cout<<"foo"<<'\n'<<((void*)&foo)<<'\n'<<foo()<<std::endl;  
    std::cout<<"boo"<<'\n'<<((void*)&boo)<<'\n'<<boo()<<std::endl;
}

Mas, por exemplo, no gcc https://godbolt.org/z/dQXmHm com a -O3função de nível de otimização pode estar embutido.

majkrzak
fonte
2
Eu adoraria votar, mas não gosto muito de asm e não entendo o que está acontecendo aqui. Alguma explicação do mecanismo como ele funciona seria realmente valiosa. Além disso, o que você quer dizer com "endereços próximos à função"? Existe algum deslocamento constante / indefinido?
R2RT 07/11
2
@majkrzak Esta não é a resposta "verdadeira", pois é a menos portátil de todas as postadas. Também não é garantido que você retorne o endereço da própria lambda.
anónimo
ele afirma que sim, mas "não é possível" resposta é falsa asnwer
majkrzak
O ponteiro de instruções não pode ser usado para derivar endereços de objetos com thread_localduração automática ou de armazenamento. O que você está tentando obter aqui é o endereço de retorno da função, não um objeto. Mas mesmo isso não funcionará porque o prólogo da função gerada pelo compilador envia para a pilha e ajusta o ponteiro da pilha para liberar espaço para variáveis ​​locais.
Maxim Egorushkin