4

我要实现一组类模板和两个特殊变量,_1以及_2.

他们应该将以下内容作为法律法规:

// Sort ascending
std::sort(a, a+5, _1 > _2);

// Output to a stream
std::for_each(a, a+5, std::cout << _1 << " ");

// Assign 100 to each element
std::for_each(a, a+5, _1 = 100);

// Print elements increased by five 5
std::transform(a, a+5, std::ostream_iterator<int>(std::cout, " "), _1 + 5);

我想 _1 * 5 也应该产生一个一元函数,以及 _1 / 5 等。

  • 不允许升压
  • 不允许使用 lambda

现在我对模板和模板元编程的经验很少,所以我什至不知道从哪里开始以及我的类模板的结构应该是什么样子。我特别困惑,因为我不知道在我的类模板中是否必须分别为所有这些operator=, operator>>, operator+, ...-,...*编写实现.../- 或者有更通用的方法来做到这一点。

我将特别感谢这些运算符的实施示例的答案;模板对我来说仍然是一团糟。

4

2 回答 2

5

Well! That is a tricky homework problem, indeed! But, it's also a very good problem to work on and to learn from.

I think that the best way to answer this is for you to start off with simple use cases and incrementally build up your solution.

For example, suppose that you have the following std::vector<int> to work with:

std::vector<int> vec;
vec.push_back(4);
vec.push_back(-8);
vec.push_back(1);
vec.push_back(0);
vec.push_back(7);

You'll obviously want to allow the following use case:

std::for_each(vec.cbegin(), vec.cend(), _1);

But how to allow this? First you'll need to define _1 and then you'll need to implement an "anything goes" overload of the function call operator for the type of _1.

The way that Boost Lambda and Boost Bind define placeholders objects _1, _2, ... is to make them have a dummy type. For example, the _1 object might have the type placeholder1_t:

struct placeholder1_t { };
placeholder1_t _1;

struct placeholder2_t { };
placeholder2_t _2;

Such a "dummy type" is frequently, informally called a tag type. There are many C++ libraries and indeed the STL that rely on tag types (e.g. std::nothrow_t). They are used to pick the "right" function overload to execute. Essentially, dummy objects are created having a tag type and these are passed into a function. The function does not use the dummy object in any way (in fact, most of the time a parameter name is not even specified for it), but by the existence of that extra parameter, the compiler is able to pick the correct overload to call.

Let's extend the definition of placeholder1_t by adding overloads of the function call operator. Remember that we want it to accept anything, so the overloads of the function call operator will themselves be templated on the argument type:

struct placeholder1_t
{
    template <typename ArgT>
    ArgT& operator()(ArgT& arg) const {
        return arg;
    }

    template <typename ArgT>
    const ArgT& operator()(const ArgT& arg) const {
        return arg;
    }
};

That's it! Our simplest of use cases will now compile and run:

std::for_each(vec.cbegin(), vec.cend(), _1);

Of course, it basically amounts to a no-op.

Let's now work on _1 + 5. What should that expression do? It should return a unary functional object that, when invoked with an argument (of some unknown type), the result is that argument plus 5. Making this more generic, the expression is unary-functional-object + object. The returned object is itself a unary functional object.

The type of the returned object needs to be defined. It will be a template with two template type parameters: the unary functional type and the type of the object that is being added to the result of the unary functional:

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t;

"partfn" refers to a functional type representing partial application of the binary + operator. Instances of this type need a copy of the unary functional object (having type UnaryFnT) and the other object (having type ObjT):

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t
{
    UnaryFnT m_fn;
    ObjT m_obj;

    unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj)
        : m_fn(fn), m_obj(obj)
    {
    }
};

Okay. The function call operator also needs to be overloaded to allow for any argument. We'll use the C++11 decltype feature to refer to the type of an expression as we don't know what it is beforehand:

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t
{
    UnaryFnT m_fn;
    ObjT m_obj;

    unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj)
        : m_fn(fn), m_obj(obj)
    {
    }

    template <typename ArgT>
    auto operator()(ArgT& arg) const -> decltype(m_fn(arg) + m_obj) {
        return m_fn(arg) + m_obj;
    }

    template <typename ArgT>
    auto operator()(const ArgT& arg) const -> decltype(m_fn(arg) + m_obj) {
        return m_fn(arg) + m_obj;
    }
};

It's starting to get complicated, but there are no surprises in this code. It essentially says that the function call operator is overloaded to accept practically any argument. It will then call m_fn (the unary functional object) on the argument and add m_obj to the result. The return type is the decltype of m_fn(arg) + m_obj.

Now that the type is defined, we can write the overload of binary operator + accepting an object of type placeholder1_t on the left:

template <typename ObjT>
inline unary_plus_object_partfn_t<placeholder1_t, ObjT> operator+(const placeholder1_t& fn, ObjT obj)
{
    return unary_plus_object_partfn_t<placeholder1_t, ObjT>(fn, obj);
}

We now can compile and run the second use case:

std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5);
std::cout << std::endl;

which outputs:

9 -3 6 5 12

This is basically all that you need to do to solve the problem. Think about how you can write custom functional types, instances of which can be returned by overloads of operators.

EDIT: Improved the overloads of function call operators by employing pass-by-reference.

EDIT2: In some cases it will be necessary to store a reference to an object rather than a copy of it. For example, to accommodate std::cout << _1, you will need to store a reference to std::cout in the resulting functional object because the std::ios_base copy constructor is private, and it is impossible to copy construct objects of any class derived from std::ios_base including std::ostream.

To allow for std::cout << _1, you might want to write a ref_insert_unary_partfn_t template. Such a template, like the example of unary_plus_object_partfn_t above, would be templated on an object type and a unary functional type:

template <typename ObjT, typename UnaryFnT>
struct ref_insert_unary_partfn_t;

Instances of instantiations of this template will need to store a reference to an object of type ObjT as well as a copy of a unary functional object of type UnaryFnT:

template <typename ObjT, typename UnaryFnT>
struct ref_insert_unary_partfn_t
{
    ObjT& m_ref;
    UnaryFnT m_fn;

    ref_insert_unary_partfn_t(ObjT& ref, UnaryFnT fn)
        : m_ref(ref), m_fn(fn)
    {
    }
};

Add overloads of the function call operator as before as well as overloads of the insertion operator, <<.

In the case of std::cout << _1, the returned object would have the type ref_insert_unary_partfn_t<std::basic_ostream<char>, placeholder1_t>.

于 2011-12-09T18:23:17.157 回答
2

一个简单的例子:

template <typename T>
class Parameter
{
};

template <typename T>
struct Ascending
{
    bool operator()(T left, T right)
    {
        return left < right;
    }
};

template <typename T>
Ascending<T> operator > (Parameter<T> p1, Parameter<T> p2)
{
    return Ascending<T>();
}

int main()
{

    std::vector<int> vec;
    vec.push_back(3);
    vec.push_back(6);
    vec.push_back(7);
    vec.push_back(2);
    vec.push_back(7);

    std::vector<int>::iterator a = vec.begin();

    Parameter<int> _1;
    Parameter<int> _2;

    std::sort(a, a+4, _1 > _2);
}
于 2011-12-09T16:07:58.310 回答