6

この関数の関数ポインタと引数を格納できるテンプレート クラスを作成して、関数を後でこの引数で呼び出すことができるようにしたいと考えています。

引数の型や数に依存せず、普遍的に書きたいと思います。

これは、c++ 11 の可変個引数テンプレートを使用したアイデアの一部です。

template<class T, typename... Params>
class LazyEvaluation {
private:
    // Function to be invoked later
    T (*f)(Params...);
    // Params for function f
    Params... storedParams; // This line is not compilable!
    bool evaluated;
    T result;
public:
    // Constructor remembers function pointer and parameters
    LazyEvaluation(T (*f)(Params...),Params... params)
    : f(f),
    storedParams(params) //this line also cannot be compiled
    {}
    // Method which can be called later to evaluate stored function with stored arguments
    operator T&() {
            // if not evaluated then evaluate
            if (! evaluated) {
                    result = f(storedParams...);
                    evaluated = true;
            }
            return result;
     }
}

可能であれば、少なくともこのクラス型のパブリック インターフェイスを安全にしたいと考えています。少なくとも何らかの形でこの作業を行うことはより重要ですが。

可変数の引数をどうにかして保存することができました。しかし、それらを関数 f に渡すことができませんでした。私はそれを回答に書きますが、私の醜いうまくいかない試みを見る前に、あなた自身の解決策について考えてほしい.

上記のコードを Microsoft Visual C++ Compiler Nov 2012 CTP (v120_CTP_Nov2012) でコンパイルしようとしていますが、コンパイラに依存しないソリューションが存在することが最善です。

ありがとうございました

4

3 に答える 3

1

これが私がそれを解決しようとした方法です:

パラメータパックは再帰的に展開でき、各パラメータを保存できます。関数ストアがそれを行うことになっています。1 つの (2 回オーバーロードされた) ヘルパー関数を使用します。

template<typename T>
void storeHelperFunction(void*& memory, T last) {
    *((T*)memory) = last;
    memory = (void*)((char*)memory + sizeof(T));
}

template<typename T, typename... Params>
void storeHelperFunction(void*& memory, T first, Params... rest) {
    storeHelperFunction(memory, first);
    storeHelperFunction(memory, rest...);
}

template<typename... Params>
void store(void* memory, Params... args) {
    // Copy of pointer to memory was done when passing it to this function
    storeHelperFunction(memory, args...);
}

関数 store は、変数の数の引数が保存されるはずのメモリへのポインタを取ります。

ポインタは、動的に割り当てられたメモリ、またはサイズが に等しい構造体を指すことができますsizeof...(Params)。テンプレートのメタプログラミングを使用して、正確に任意のサイズを持つこのような構造を構築できます。

template <int N>
struct allocatorStruct {
    char byte1;
    allocatorStruct<N-1> next;
};

template <>
struct allocatorStruct<1> {};

標準が何を言っているのか、またはMicrosoft以外のコンパイラがどのようにコンパイルするのかわかりません。しかし、私のコンパイラを使用すると、sizeof(allocatorStruct) は、1 以上の任意の N に対して N に等しくなります。

したがってallocatorStruct<sizeof...(Params)>、Params と同じサイズになります。

Params と同じサイズのものを作成する別の方法は、 type を使用することchar [sizeof...(Params)]です。これには、このような配列を引数として渡そうとすると、コンパイラがこの配列へのポインターのみを渡すという欠点があります。そのため、 を使用することをお勧めしますallocatorStruct<sizeof...(Params)>

そして今、主なアイデア:

関数を保存するときに、次のようにキャストできますT (*)(allocatorStruct<sizeof...(Params)>)。関数の引数を保存するとき、タイプ struct に保存できますallocatorStruct<sizeof...(Params)>

引数のサイズは同じです。関数ポインタは関数の型に関するものですが、関数が指すデータは正しく取得されます。

少なくとも私は望んでいました。呼び出し規約によっては、引数を左から右に保存する場合と右から左に渡す場合の違いにより、渡された引数が並べ替えられたり間違ったりする可能性があると予想しました。しかし、そうではありませんでした。__cdecl 呼び出し規約を使用すると、最初の引数のみが渡され、他の引数は失われました。他の呼び出し規約では、プログラムは機能しなくなりました。

私はそれをデバッグし、メモリ内(スタック上)のデータを調べるのに多くの時間を費やしませんでした。少なくとも正しい方法ですか?

于 2013-08-01T14:18:43.600 に答える
1

単純にラムダ式を使用する

// Some function.
int add(int a, int b) {
    return a + b;
}

auto lazyFunc = [] { return add(1, 2); };

std::cout << lazyFunc() << std::endl; // Evaluate function and output result.

可変個引数テンプレートを使用して関数を 1 回だけ (遅延的に) 評価するクラスを本当に作成したい場合は、次のコードのようにすることができます。

また、パラメーターが変更されるたびに新しいインスタンスを作成する必要がないように、クラスを作成しました。a を使用しstd::tupleて、指定された引数を格納し、以前に指定された引数と比較します。引数が異なる場合、関数は再評価されます。

関数はラッパーを使用して渡され、保存されるstd::functionため、生の関数ポインターを操作する必要はありません (yuck)。

#include <iostream>
#include <functional>
#include <utility>
#include <tuple>

template <typename T>
class LazyEvaluation {};

template <typename ReturnType, typename... Params>
class LazyEvaluation<ReturnType(Params...)> {
private:
    std::function<ReturnType(Params...)> func_;
    ReturnType result;
    std::tuple<Params...> oldParams; // Contains the previous arguments.
public:
    explicit LazyEvaluation(std::function<ReturnType(Params...)> func)
        : func_(std::move(func)) {}
    template <typename... Args>
    ReturnType operator() (Args&&... args) {
        auto newParams = std::make_tuple(std::forward<Args>(args)...);

        // Check if new arguments.
        if (newParams != oldParams) {
            result = func_(std::forward<Args>(args)...);
            oldParams = newParams;
            std::cout << "Function evaluated" << std::endl;
        }

        std::cout << "Returned result" << std::endl;
        return result;
    }
};

int main() {
    auto f = [] (int a, int b) {
        return a + b;
    };

    // Specify function type as template parameter.
    // E.g. ReturnType(Param1Type, Param2Type, ..., ParamNType)
    LazyEvaluation<int(int, int)> ld(f);

    std::cout << ld(1, 2) << std::endl;
    std::cout << ld(1, 2) << std::endl;
    std::cout << ld(3, 4) << std::endl;
}

出力:

Function evaluated
Returned result
3
Returned result
3
Function evaluated
Returned result
7
于 2013-08-01T14:39:38.180 に答える
0

可変長インデックス パックを形成するための標準的な機構を考えると、次のようになります。

template <std::size_t... I> struct index_sequence {};
template <std::size_t N, std::size_t... I>
struct make_index_sequence : public make_index_sequence<N-1, N-1, I...> {};
template <std::size_t... I>
struct make_index_sequence<0, I...> : public index_sequence<I...> {};

アンパックされたタプル引数で関数を呼び出すには:

template <typename Function, typename... Types, std::size_t... I>
auto apply_(Function&& f, const std::tuple<Types...>& t, index_sequence<I...>)
  -> decltype(std::forward<Function>(f)(std::get<I>(t)...)) {
  return std::forward<Function>(f)(std::get<I>(t)...);
}

template <typename Function, typename... Types>
auto apply(Function&& f, const std::tuple<Types...>& t)
  -> decltype(apply_(f, t, make_index_sequence<sizeof...(Types)>())) {
  return apply_(f, t, make_index_sequence<sizeof...(Types)>());
}

これはかなり簡単です。

template<typename Function, typename... Params>
class LazyEvaluation {
private:
  typedef decltype(std::declval<Function>()(std::declval<Params>()...)) result_type;
  // Function to be invoked later
  Function f;
  // Params for function f
  std::tuple<Params...> storedParams;
  mutable bool evaluated;
  union {
    std::aligned_storage<sizeof(result_type)> space;
    mutable result_type result;
  };

  // Method which can be called later to evaluate stored function with stored arguments
  void evaluate() const {
    // if not evaluated then evaluate
    if (! evaluated) {
      new (&result) result_type{apply(f, storedParams)};
      evaluated = true;
    }
  }

public:
  // Constructor remembers function pointer and parameters
  LazyEvaluation(Function f, Params... params)
    : f(std::move(f)),
      storedParams(std::move(params)...),
      evaluated(false)
  {}
  ~LazyEvaluation() {
    if (evaluated)
      result.~result_type();
  }

  operator result_type&() {
    evaluate();
    return result;
  }

  operator const result_type& () const {
    evaluate();
    return result;
  }
};

template <typename Function, typename... Params>
LazyEvaluation<Function, Params...>
make_lazy(Function&& f, Params&&... params) {
  return {std::forward<Function>(f), std::forward<Params>(params)...};
}

デフォルトで構築可能な型である必要newがないように、評価の結果を格納するために共用体と配置を使用しました。mutableconst LazyEvaluator

于 2013-08-01T15:10:05.607 に答える