Lambda kendini geri veriyor: bu yasal mı?


125

Oldukça yararsız olan bu programı düşünün:

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self);
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

Temel olarak, kendisini döndüren bir lambda yapmaya çalışıyoruz.

  • MSVC programı derler ve çalışır
  • gcc programı derler ve segfaults
  • clang, programı bir mesajla reddeder:

    error: function 'operator()<(lambda at lam.cpp:6:13)>' with deduced return type cannot be used before it is defined

Hangi derleyici doğru? Statik bir kısıtlama ihlali var mı, UB mi yoksa ikisi de yok mu?

Güncelleme bu küçük değişikliği clang tarafından kabul edildi:

  auto it = [&](auto& self, auto b) {
          std::cout << (a + b) << std::endl;
          return [&](auto p) { return self(self,p); };
  };
  it(it,4)(6)(42)(77)(999);

Güncelleme 2 : Bunu başarmak için kendi kendine dönen bir işlevin nasıl yazılacağını veya Y birleştiricinin nasıl kullanılacağını anlıyorum. Bu daha çok bir dil-avukat sorusu.

3 Güncelleme : sorudur değil bir lambda genel olarak kendisini dönmek için yasal olup olmadığı, fakat bunu yapmanın bu belirli bir şekilde yasallığı konusunda.

İlgili soru: C ++ lambda kendini döndürüyor .


2
clang şu anda daha düzgün görünüyor, merak ediyorum böyle bir yapı kontrol bile yazabilir mi, daha çok sonsuz bir ağaçta biter.
2018

2
Bunun bir dil avukatı sorusu olduğunu söyleyen yasal olup olmadığını soruyorsun, ancak cevapların birçoğu bu yaklaşımı benimsemiyor ... etiketleri doğru yapmak önemlidir
Shafik Yaghmour

2
@ShafikYaghmour Teşekkürler, bir etiket ekledim
n. zamirler 'm.

1
@ArneVogel evet, auto& selfsarkan referans problemini ortadan kaldıran güncellenmiş olanı kullanır .
n. zamirler 'm.

1
@TheGreatDuck C ++ lambdalar gerçekten teorik lambda ifadeleri değildir. C ++, orijinal basit tip lambda hesaplamasının ifade edemediği yerleşik özyinelemeli türlere sahiptir, bu nedenle, a: a-> a ve diğer imkansız yapılarla eşbiçimli şeyler olabilir.
n. zamirler 'm.

Yanıtlar:


69

Program, [dcl.spec.auto] / 9 uyarınca biçimsizdir (clang doğrudur) :

Eğitimsiz yer tutucu türüne sahip bir varlığın adı bir ifadede görünüyorsa, program bozuktur. Ancak, bir işlevde atılmamış bir return ifadesi görüldüğünde, bu ifadeden çıkarılan dönüş türü, diğer dönüş ifadeleri de dahil olmak üzere işlevin geri kalanında kullanılabilir.

Temel olarak, iç lambda'nın dönüş türünün kesintisi kendisine bağlıdır (burada adı geçen varlık çağrı operatörüdür) - bu nedenle açıkça bir dönüş türü sağlamanız gerekir. Bu özel durumda, bu imkansız, çünkü iç lambda tipine ihtiyacınız var ama adını koyamıyorsunuz. Ancak bunun gibi yinelemeli lambdaları zorlamaya çalışmanın işe yarayabileceği başka durumlar da vardır.

O olmasa bile, sarkan bir referansınız var .


Daha akıllı biriyle (yani TC) tartıştıktan sonra biraz daha detaylandırmama izin verin Orijinal kod (biraz azaltılmış) ile önerilen yeni sürüm (aynı şekilde azaltılmış) arasında önemli bir fark vardır:

auto f1 = [&](auto& self) {
  return [&](auto) { return self(self); } /* #1 */ ; /* #2 */
};
f1(f1)(0);

auto f2 = [&](auto& self, auto) {
  return [&](auto p) { return self(self,p); };
};
f2(f2, 0);

Ve bu, içsel ifadenin self(self)bağımlı f1değil self(self, p), bağımlı olduğu yönündedir f2. İfadeler bağımlı olmadığında, ... hevesle kullanılabilirler ( [temp.res] / 8 , örneğin static_assert(false), içinde bulduğu şablonun somutlaştırılmış olup olmadığına bakılmaksızın, nasıl zor bir hata olur).

Çünkü f1, bir derleyici (örneğin, clang) bunu hevesle somutlaştırmaya çalışabilir. Yukarıdaki ;noktaya geldiğinizde dış lambda'nın çıkarılan türünü biliyorsunuz #2(bu iç lambda'nın türü), ama biz onu daha önce kullanmaya çalışıyoruz (bir noktada olduğu gibi düşünün #1) - deniyoruz iç lambdayı hala ayrıştırırken, türünün gerçekte ne olduğunu bilmeden önce kullanmak için. Bu, dcl.spec.auto/9 ile ters düşer.

Ancak, f2bağımlı olduğu için hevesle somutlaştırmaya çalışamayız. Sadece kullanım noktasında somutlaştırabiliriz, bu noktada her şeyi biliyoruz.


Gerçekten böyle bir şey yapmak için bir y-birleştiriciye ihtiyacınız var . Kağıttan uygulama:

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

Ve istediğin şey:

auto it = y_combinator([&](auto self, auto b){
    std::cout << (a + b) << std::endl;
    return self;
});

İade türünü açıkça nasıl belirtirsiniz? Çözemiyorum.
Rakete1111

@ Rakete1111 Hangisi? Orijinalde yapamazsınız.
Barry

Ah tamam. Ben yerli değilim, ancak "bu yüzden açıkça bir dönüş türü sağlamanız gerekiyor" bir yol olduğunu ima ediyor, bu yüzden soruyordum :)
Rakete1111

4
@PedroA stackoverflow.com/users/2756719/tc bir C ++ katılımcısıdır. Aynı zamanda bir yapay zeka değil ya da C ++ hakkında bilgi sahibi olan bir insanı Chicago'daki son LWG mini toplantısına katılmaya ikna edecek kadar becerikli.
Casey

3
@Casey Ya da belki insan sadece yapay zekanın ona söylediklerini papağan ediyordur ... asla bilemezsiniz;)
TC

34

Düzenleme : Bu yapının C ++ şartnamesine göre kesinlikle geçerli olup olmadığı konusunda bazı tartışmalar var gibi görünüyor. Hakim görüş, geçerli olmadığı gibi görünüyor. Daha kapsamlı bir tartışma için diğer cevaplara bakın. Bu cevabın kalan geçerlidir eğer inşaat geçerlidir; Aşağıdaki ince ayarlı kod MSVC ++ ve gcc ile çalışır ve OP, clang ile de çalışan daha fazla değiştirilmiş kod yayınlamıştır.

Bu tanımsız bir davranıştır, çünkü içteki lambda parametreyi selfreferans olarak yakalar , ancak 7. satırdan selfsonra kapsam dışına çıkar return. Böylece, döndürülen lambda daha sonra çalıştırıldığında, kapsam dışına çıkan bir değişkene bir referansa erişir.

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self); // <-- using reference to 'self'
      };
  };
  it(it)(4)(6)(42)(77)(999); // <-- 'self' is now out of scope
}

Programı ile çalıştırmak şunu valgrindgöstermektedir:

==5485== Memcheck, a memory error detector
==5485== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5485== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5485== Command: ./test
==5485== 
9
==5485== Use of uninitialised value of size 8
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485== 
==5485== Invalid read of size 4
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485==  Address 0x4fefffdc4 is not stack'd, malloc'd or (recently) free'd
==5485== 
==5485== 
==5485== Process terminating with default action of signal 11 (SIGSEGV)
==5485==  Access not within mapped region at address 0x4FEFFFDC4
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485==  If you believe this happened as a result of a stack
==5485==  overflow in your program's main thread (unlikely but
==5485==  possible), you can try to increase the size of the
==5485==  main thread stack using the --main-stacksize= flag.
==5485==  The main thread stack size used in this run was 8388608.

Bunun yerine, dış lambdayı değere göre değil de referans alarak kendini alacak şekilde değiştirebilir, böylece bir sürü gereksiz kopyadan kaçınarak sorunu çözebilirsiniz:

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto& self) { // <-- self is now a reference
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self);
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

Bu çalışıyor:

==5492== Memcheck, a memory error detector
==5492== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5492== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5492== Command: ./test
==5492== 
9
11
47
82
1004

Genel lambdalara aşina değilim, ama selfbir referans yapamaz mısın ?
François Andrieux

@ FrançoisAndrieux Evet, selfbir referans yaparsanız , bu sorun ortadan kalkar, ancak Clang yine de başka bir nedenle reddeder
Justin

@ FrançoisAndrieux Indeed ve bunu cevaba ekledim, teşekkür ederim!
TypeIA

Bu yaklaşımla ilgili sorun, olası derleyici hatalarını ortadan kaldırmamasıdır. Yani belki işe yaramalı ama uygulama bozuk.
Shafik Yaghmour

Teşekkürler, buna saatlerce baktım ve selfreferansla yakalandığını görmedim !
n. zamirler 'm.

21

TL; DR;

clang doğru.

Görünüşe göre standardın bu kötü biçimlendirilmiş bölümü [dcl.spec.auto] p9 :

Eğitimsiz yer tutucu türüne sahip bir varlığın adı bir ifadede görünüyorsa, program bozuktur. Ancak, bir işlevde atılmamış bir return ifadesi görüldüğünde, bu ifadeden çıkarılan dönüş türü, diğer return ifadeleri de dahil olmak üzere, işlevin geri kalanında kullanılabilir. [ Misal:

auto n = n; // error, n’s initializer refers to n
auto f();
void g() { &f; } // error, f’s return type is unknown

auto sum(int i) {
  if (i == 1)
    return i; // sum’s return type is int
  else
    return sum(i-1)+i; // OK, sum’s return type has been deduced
}

—Son örnek]

Orijinal çalışma boyunca

Standart Kitaplığa Y Combinator Ekleme Önerisi teklifine bakarsak, çalışan bir çözüm sunar:

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

ve açıkça örneğinizin mümkün olmadığını söylüyor:

C ++ 11/14 lambdalar özyinelemeyi teşvik etmez: lambda nesnesine lambda işlevinin gövdesinden başvurmanın bir yolu yoktur.

ve Richard Smith'in clang'ın size verdiği hatayı ima ettiği bir tartışmaya atıfta bulunuyor :

Bunun birinci sınıf bir dil özelliği olarak daha iyi olacağını düşünüyorum. Kona öncesi toplantı için zamanım kalmadı, ancak bir lambda'ya bir isim vermesine izin vermek için bir makale yazmayı planlıyordum (kapsamı kendi vücuduna göre):

auto x = []fib(int a) { return a > 1 ? fib(a - 1) + fib(a - 2) : a; };

Burada, 'fib' lambda'nın * this ile eşdeğerdir (lambda'nın kapanma türü eksik olmasına rağmen bunun çalışmasına izin veren bazı can sıkıcı özel kurallarla).

Barry beni, bunun neden mümkün olmadığını ve kısıtlama etrafında çalıştığını açıklayan ve aynı zamanda bugün onsuz bunu başarmak için yöntemler gösteren Yinelemeli lambdas önerisine işaret dcl.spec.auto#9etti:

Lambdalar, yerel kod yeniden düzenleme için kullanışlı bir araçtır. Bununla birlikte, bazen lambda'yı kendi içinde kullanmak isteriz, ya doğrudan özyinelemeye izin vermek ya da kapanışın bir devamı olarak kaydedilmesine izin vermek için. Şaşırtıcı derecede güncel C ++ 'da bunu başarmak zor.

Misal:

  void read(Socket sock, OutputBuffer buff) {
  sock.readsome([&] (Data data) {
  buff.append(data);
  sock.readsome(/*current lambda*/);
}).get();

}

Bir lambdayı kendisinden referans almak için doğal bir girişim, onu bir değişkende saklamak ve bu değişkeni referansla yakalamaktır:

 auto on_read = [&] (Data data) {
  buff.append(data);
  sock.readsome(on_read);
};

Bununla birlikte, semantik döngüsellik nedeniyle bu mümkün değildir : auto değişkeninin türü, lambda ifadesi işlenene kadar çıkarılmaz, yani lambda ifadesi değişkene başvuramaz.

Başka bir doğal yaklaşım da std :: function kullanmaktır:

 std::function on_read = [&] (Data data) {
  buff.append(data);
  sock.readsome(on_read);
};

Bu yaklaşım derler, ancak tipik olarak bir soyutlama cezası getirir: std :: işlevi bir bellek tahsisine neden olabilir ve lambda'nın çağrılması tipik olarak dolaylı bir çağrı gerektirir.

Sıfır ek yük çözümü için, genellikle yerel bir sınıf türünü açıkça tanımlamaktan daha iyi bir yaklaşım yoktur.


@ Cheersandhth.-Alf Makaleyi okuduktan sonra standart alıntıyı buldum, bu yüzden konuyla ilgili değil çünkü standart alıntı her iki yaklaşımın da neden işe
yaramadığını açıklıyor

"" Eğitimsiz yer tutucu türüne sahip bir varlığın adı bir ifadede görünüyorsa, program kötü biçimlendirilmiş "Yine de programda bunun bir oluşumunu görmüyorum. selfBöyle bir varlık görünmüyor.
n. 'zamirler' m.

@nm olası ifadelerin yanı sıra örneklerle anlam ifade ediyor gibi görünüyor ve örneklerin sorunu açıkça gösterdiğine inanıyorum. Şu anda yardımcı olmak için daha fazlasını ekleyebileceğimi sanmıyorum.
Shafik Yaghmour

13

Görünüşe göre clang haklı. Basitleştirilmiş bir örnek düşünün:

auto it = [](auto& self) {
    return [&self]() {
      return self(self);
    };
};
it(it);

Bunu bir derleyici gibi inceleyelim (biraz):

  • Türü itolan Lambda1bir şablon çağrı operatörü ile.
  • it(it); çağrı operatörünün somutlaştırılmasını tetikler
  • Şablon çağrı operatörünün dönüş türü auto, bu yüzden onu çıkarmalıyız.
  • Tipin ilk parametresini yakalayan bir lambda döndürüyoruz Lambda1.
  • Lambda'nın da çağrının türünü döndüren bir çağrı operatörü vardır. self(self)
  • Uyarı: self(self)tam olarak başladığımız şey!

Bu nedenle, tür çıkarılamaz.


Dönüş türü Lambda1::operator()basitçe Lambda2. Daha sonra bu iç lambda ifadesinin içinde self(self), bir çağrının dönüş türünün de Lambda1::operator()olduğu bilinir Lambda2. Muhtemelen biçimsel kurallar bu önemsiz çıkarımı yapmanın önünde duruyor, ancak burada sunulan mantık öyle değil. Buradaki mantık sadece bir iddia anlamına gelir. Resmi kurallar önünüze çıkarsa, bu resmi kurallardaki bir kusurdur.
Şerefe ve hth. - Alf

@ Cheersandhth.-Alf Dönüş türünün Lambda2 olduğunu kabul ediyorum, ancak eğitimsiz bir çağrı operatörüne sahip olamayacağınızı biliyorsunuz çünkü önerdiğiniz şey bu: Lambda2'nin çağrı operatörü dönüş türünün kesintisini geciktirin. Ancak oldukça temel olduğu için bunun kurallarını değiştiremezsiniz.
Rakete1111

9

Eh, kodunuz çalışmıyor. Ancak bu:

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

Test kodu:

ycombinator bob = {[x=0](auto&& self)mutable{
  std::cout << ++x << "\n";
  ycombinator ret = {self};
  return ret;
}};

bob()()(); // prints 1 2 3

Kodunuz hem UB hem de kötü biçimlendirilmiş teşhis gerektirmez. Komik olan; ancak her ikisi de bağımsız olarak sabitlenebilir.

İlk olarak, UB:

auto it = [&](auto self) { // outer
  return [&](auto b) { // inner
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};
it(it)(4)(5)(6);

bu UB'dir çünkü dış selfdeğere göre alır , daha sonra selfreferansla iç yakalar , ardından outerçalışmayı bitirdikten sonra onu geri döndürmeye devam eder . Yani segfaulting kesinlikle sorun değil.

Çözüm:

[&](auto self) {
  return [self,&a](auto b) {
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};

Geriye kalan kod bozuk. Bunu görmek için lambdaları genişletebiliriz:

struct __outer_lambda__ {
  template<class T>
  auto operator()(T self) const {
    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      T self;
    };
    return __inner_lambda__{a, self};
  }
  int& a;
};
__outer_lambda__ it{a};
it(it);

bu örnekler __outer_lambda__::operator()<__outer_lambda__>:

  template<>
  auto __outer_lambda__::operator()(__outer_lambda__ self) const {
    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      __outer_lambda__ self;
    };
    return __inner_lambda__{a, self};
  }
  int& a;
};

Öyleyse şimdi dönüş türünü belirlememiz gerekiyor __outer_lambda__::operator().

Satır satır geçiyoruz. İlk önce __inner_lambda__türü oluşturuyoruz :

    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      __outer_lambda__ self;
    };

Şimdi, şuraya bakın - dönüş türü self(self), veya __outer_lambda__(__outer_lambda__ const&). Ama dönüş türünü anlamaya çalışmanın tam ortasındayız __outer_lambda__::operator()(__outer_lambda__).

Bunu yapmana izin yok.

Aslında dönüş türü aslında dönüş türüne bağlı __outer_lambda__::operator()(__outer_lambda__)olmasa da __inner_lambda__::operator()(int), C ++ dönüş türlerini çıkarırken umursamaz; sadece kodu satır satır kontrol eder.

Ve self(self)biz onu çıkarmadan önce kullanılır. Kötü biçimlenmiş program.

Bunu self(self)daha sonrasına kadar saklayarak düzeltebiliriz :

template<class A, class B>
struct second_type_helper { using result=B; };

template<class A, class B>
using second_type = typename second_type_helper<A,B>::result;

int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [self,&a](auto b) {
        std::cout << (a + b) << std::endl;
        return self(second_type<decltype(b), decltype(self)&>(self) );
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

ve şimdi kod doğrudur ve derlenir. Ama bunun biraz hile olduğunu düşünüyorum; sadece ycombinator'ı kullanın.


Muhtemelen (IDK) bu açıklama lambdalar hakkındaki resmi kurallar için doğrudur. Ancak şablonun yeniden yazılması açısından, iç lambda'nın geri dönüş türü, operator()somutlaştırılıncaya kadar (bir tür argümanla çağrılarak) genel olarak çıkarılamaz. Ve böylece şablon tabanlı koda manuel makine benzeri bir yeniden yazma işlemi güzel bir şekilde çalışıyor.
Şerefe ve hth. - Alf

@cheers kodunuz farklıdır; iç, kodunuzdaki bir şablon sınıfıdır, ancak benim veya OP kodunda değildir. Ve bu önemlidir, çünkü şablon sınıf yöntemleri çağrılana kadar gecikmeli olarak başlatılır.
Yakk - Adam Nevraumont

Templated bir işlev içinde tanımlanan bir sınıf, bu işlevin dışındaki bir templated sınıfa eşdeğerdir. C ++ kuralları, yerel kullanıcı tanımlı bir sınıfta bir üye şablonuna izin vermediğinden, onu işlevin dışında tanımlamak, şablonlu bir üye işlevi olduğunda demo kodu için gereklidir. Bu resmi kısıtlama, derleyicinin kendi ürettiği şey için geçerli değildir.
Şerefe ve hth. - Alf

7

Kodu, lambda ifadeleri için bir derleyicinin oluşturacağı veya daha çok üretmesi gereken sınıflar açısından yeniden yazmak yeterince kolaydır.

Bu yapıldığında, asıl sorunun sadece sarkan referans olduğu ve kodu kabul etmeyen bir derleyicinin lambda bölümünde biraz zorlandığı açıktır.

Yeniden yazma, döngüsel bağımlılıkların olmadığını gösterir.

#include <iostream>

struct Outer
{
    int& a;

    // Actually a templated argument, but always called with `Outer`.
    template< class Arg >
    auto operator()( Arg& self ) const
        //-> Inner
    {
        return Inner( a, self );    //! Original code has dangling ref here.
    }

    struct Inner
    {
        int& a;
        Outer& self;

        // Actually a templated argument, but always called with `int`.
        template< class Arg >
        auto operator()( Arg b ) const
            //-> Inner
        {
            std::cout << (a + b) << std::endl;
            return self( self );
        }

        Inner( int& an_a, Outer& a_self ): a( an_a ), self( a_self ) {}
    };

    Outer( int& ref ): a( ref ) {}
};

int main() {

  int a = 5;

  auto&& it = Outer( a );
  it(it)(4)(6)(42)(77)(999);
}

Orijinal koddaki iç lambda'nın şablonlu tipte bir öğeyi yakalama şeklini yansıtan tamamen şablonlu bir sürüm:

#include <iostream>

struct Outer
{
    int& a;

    template< class > class Inner;

    // Actually a templated argument, but always called with `Outer`.
    template< class Arg >
    auto operator()( Arg& self ) const
        //-> Inner
    {
        return Inner<Arg>( a, self );    //! Original code has dangling ref here.
    }

    template< class Self >
    struct Inner
    {
        int& a;
        Self& self;

        // Actually a templated argument, but always called with `int`.
        template< class Arg >
        auto operator()( Arg b ) const
            //-> Inner
        {
            std::cout << (a + b) << std::endl;
            return self( self );
        }

        Inner( int& an_a, Self& a_self ): a( an_a ), self( a_self ) {}
    };

    Outer( int& ref ): a( ref ) {}
};

int main() {

  int a = 5;

  auto&& it = Outer( a );
  it(it)(4)(6)(42)(77)(999);
}

Sanırım, biçimsel kuralların yasaklamak için tasarlandığı, iç mekanizmadaki bu şablonlama. Orijinal yapıyı yasaklarlarsa.


Gördün mü, sorun şu ki bu template< class > class Inner;şablon operator()... somutlaştırılmış mı? Yanlış kelime. Yazılı? ... Outer::operator()<Outer>dış operatörün dönüş türü çıkarılmadan önce. Ve Inner<Outer>::operator()kendine bir çağrı var Outer::operator()<Outer>. Ve buna izin verilmez. Şimdi, en derleyiciler yok farkself(self) onlar dönüş türünü anlamak için beklemek çünkü Outer::Inner<Outer>::operator()<int>zaman için int. Sensible içinde geçirilir. Ama kodun kötü biçimlenmişliğini gözden kaçırıyor.
Yakk - Adam Nevraumont

Eh ben düşündüklerini gerekir o fonksiyon şablonu kadar fonksiyon şablonun dönüş türü anlamak için beklemek Innner<T>::operator()<U>, örneği. Sonuçta dönüş türü Uburaya bağlı olabilir . Öyle değil, ama genel olarak.
Şerefe ve hth. - Alf

Elbette; ancak türü eksik bir dönüş türü kesintisi ile belirlenen herhangi bir ifade yasa dışı kalır. Sadece bazı derleyiciler tembeldir ve daha sonra her şeyin çalıştığı noktaya kadar kontrol etmezler.
Yakk - Adam Nevraumont
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.