42

この回答に続いて、ラムダの有効期間のルールと、自動変換によって作成される関数ポインターの有効期間との関係について疑問に思っています。ラムダの寿命についていくつかの質問があります (例: hereおよびhere )。その場合、答えは「完全なファンクター オブジェクトを自分で記述したのとまったく同じように動作する」ですが、関数ポインターへの変換についてはどちらも対処していません。特別なケース。

私の懸念を説明するこの小さな実用的な例をまとめました。

#include <iostream>

typedef int (*func_t)(int);

// first case
func_t retFun1() {
  static auto lambda = [](int) { return 1; };
  // automatically converted to func_t
  return lambda;
}

// second case
func_t retFun2() {
  // no static
  auto lambda = [](int) { return 2; };
  // automatically converted to func_t and 
  // the local variable lambda reaches the end of its life
  return lambda;
}

int main() {
  const int a = retFun1()(0);
  const int b = retFun2()(0);
  std::cout << a << "," << b << std::endl;
  return 0;
}

これは両方のケースで明確に定義されていますか? それともretFun1()?問題は、「関数ポインターが指す関数は、ファンクター オブジェクト自体を呼び出す必要があるか、それとも別の関数で本体を再実装する必要があるか?」です。どちらも理にかなっていますが、関数ポインターへの変換には特にキャプチャーのないラムダが必要であるという事実は、実際には後者である可能性があることを示唆しています。


別の言い方をすれば、コンパイラがそのようなラムダを実装したいと思うかもしれない少なくとも 2 つの賢明な方法を見ることができます。可能な合法的な実装の 1 つは、コンパイラが次のようなコードを合成することです。

func_t retFun3() {
  struct __voodoo_magic_lambda_implementation {
    int operator()(int) const {
      return 3;
    }
    static int plainfunction(int) {
      return 3;
    }
    operator func_t() const {
      return plainfunction;
    }
  } lambda;
  return lambda;
}

その場合、 のバリアントstaticと非staticバリアントの両方でretFun問題ありません。ただし、コンパイラが次のようなラムダを実装することも合法である場合:

static int __voodoo_impl_function(int x);
static struct __voodoo_maigc_impl2 {
  int operator()(int) const {
    return 4;
  }
  operator func_t() const {
    return __voodoo_impl_function;
  }
} *__magic_functor_ptr;
static int __voodoo_impl_function(int x) {
  return (*__magic_functor_ptr)(x);
}

func_t retFun4() {
  __voodoo_maigc_impl2 lambda;
  // non-static, local lifetime
  __magic_functor_ptr = &lambda; //Or do the equivalent of this in the ctor
  return lambda;
}

その後retFun2()は未定義の動作です。

4

1 に答える 1

23

§5.1.2/6 は次のように述べています。

ラムダ キャプチャのないラムダ式のクロージャ型には、クロージャ型の関数呼び出し演算子と同じパラメーターと戻り値の型を持つ関数へのポインターへの public 非仮想非明示的な const 変換関数があります。この変換関数によって返される値は、呼び出されたときにクロージャー型の関数呼び出し演算子を呼び出すのと同じ効果を持つ関数のアドレスでなければなりません。

鉱山を強調します。

つまり、これは関数のアドレスであり、関数には有効期間がないため、いつでもその関数を自由に呼び出すことができます。あなたが持っているものはすべて明確に定義されています。

あなたがやったかのように少しです:

func_t retFun2()
{
    int __lambda0(int)
    {
        return 2;
    }

    struct
    {
        int operator(int __arg0) const
        {
            return __lambda0(__arg0);
        }

        operator decltype(__lambda0)() const
        {
            return __lambda0;
        }
    } lambda;

    return lambda; // just the address of a regular ol' function
}
于 2011-11-06T10:41:14.963 に答える