使用指针非类型模板参数?

时间:2012-11-04 16:35:21

标签: c++ templates

有没有人曾经使用过指针/引用/指向成员(非类型)的模板参数? 我不知道任何(理智/真实世界)场景,其中C ++特性应该被用作最佳实践。

演示功能(指针):

template <int* Pointer> struct SomeStruct {};
int someGlobal = 5;
SomeStruct<&someGlobal> someStruct; // legal c++ code, what's the use?

任何启蒙都会受到赞赏!

5 个答案:

答案 0 :(得分:11)

<强>指针到功能

指向成员函数和指向函数的非类型参数对某些代理人非常有用。它允许你快速代表。

例如:

#include <iostream>
struct CallIntDelegate
{
    virtual void operator()(int i) const = 0;
};

template<typename O, void (O::*func)(int)>
struct IntCaller : public CallIntDelegate
{
    IntCaller(O* obj) : object(obj) {}
    void operator()(int i) const
    {
        // This line can easily optimized by the compiler
        // in object->func(i) (= normal function call, not pointer-to-member call)
        // Pointer-to-member calls are slower than regular function calls
        (object->*func)(i);
    }
private:
    O* object;
};

void set(const CallIntDelegate& setValue)
{
    setValue(42);
}

class test
{
public:
    void printAnswer(int i)
    {
        std::cout << "The answer is " << 2 * i << "\n";
    }
};

int main()
{
    test obj;
    set(IntCaller<test,&test::printAnswer>(&obj));
}

Live example here

<强>指针到数据

您可以使用此类非类型参数来扩展变量的可见性。

例如,如果您正在编写反射库(可能对脚本非常有用),使用宏让用户为库声明其类,您可能希望将所有数据存储在复杂的结构中(可能随着时间的推移而变化),并希望有一些句柄来使用它。

示例:

#include <iostream>
#include <memory>

struct complex_struct
{
    void (*doSmth)();
};

struct complex_struct_handle
{
    // functions
    virtual void doSmth() = 0;
};

template<complex_struct* S>
struct csh_imp : public complex_struct_handle
{
    // implement function using S
    void doSmth()
    {
        // Optimization: simple pointer-to-member call,
        // instead of:
        // retrieve pointer-to-member, then call it.
        // And I think it can even be more optimized by the compiler.
        S->doSmth();
    }
};

class test
{
    public:
        /* This function is generated by some macros
           The static variable is not made at class scope
           because the initialization of static class variables
           have to be done at namespace scope.

           IE:
               class blah
               {
                   SOME_MACRO(params)
               };
           instead of:
               class blah
               {
                   SOME_MACRO1(params)
               };
               SOME_MACRO2(blah,other_params);

           The pointer-to-data template parameter allows the variable
           to be used outside of the function.
        */
        std::auto_ptr<complex_struct_handle> getHandle() const
        {
            static complex_struct myStruct = { &test::print };
            return std::auto_ptr<complex_struct_handle>(new csh_imp<&myStruct>());
        }
        static void print()
        {
            std::cout << "print 42!\n";
        }
};

int main()
{
    test obj;
    obj.getHandle()->doSmth();
}

对于auto_ptr抱歉,shared_ptr在Codepad和Ideone上都不可用。 Live example

答案 1 :(得分:5)

指向成员的指针的情况与指向数据或引用的指针大不相同。

如果要指定要调用的成员函数(或要访问的数据成员),但又不想将对象放在特定的层次结构中,则指向成员作为模板参数的指针非常有用(否则虚拟方法是通常就足够了。

例如:

#include <stdio.h>

struct Button
{
    virtual ~Button() {}
    virtual void click() = 0;
};

template<class Receiver, void (Receiver::*action)()>
struct GuiButton : Button
{
    Receiver *receiver;
    GuiButton(Receiver *receiver) : receiver(receiver) { }
    void click() { (receiver->*action)(); }
};

// Note that Foo knows nothing about the gui library    
struct Foo
{
    void Action1() { puts("Action 1\n"); }
};

int main()
{
    Foo foo;
    Button *btn = new GuiButton<Foo, &Foo::Action1>(&foo);
    btn->click();
    return 0;
}

如果您不想为访问支付额外的运行时价格,则指针或对全局对象的引用会很有用,因为模板实例化将使用常量(加载时解析)地址访问指定对象,而不是间接访问使用常规指针或引用会发生访问。 然而,要支付的价格是每个对象的新模板实例化,实际上很难想到一个有用的真实案例。

答案 2 :(得分:3)

Performance TR有一些例子,其中使用非类型模板来抽象硬件的访问方式(硬件内容从第90页开始;使用指针作为模板参数,例如,第113页) 。例如,注册的内存映射I / O将使用指向硬件区域的固定指针。虽然我自己没有使用过它(我只是展示了Jan Kristofferson如何做到这一点)但我非常确定它是用于开发某些嵌入式设备的。

答案 3 :(得分:2)

有时您需要提供具有特定签名作为函数指针的回调函数(例如void (*)(int)),但您要提供的函数采用不同(尽管兼容)的参数(例如double my_callback(double x)) ,所以你不能直接传递它的地址。另外,您可能希望在调用函数之前和之后做一些工作。

编写一个隐藏函数指针的类模板很容易,然后从它的operator()()或其他一些成员函数中调用它,但这并没有提供一种提取常规函数指针的方法,因为被调用的实体仍然需要this指针来查找回调函数。

您可以通过构建一个适配器以优雅和类型安全的方式解决此问题,该适配器在给定输入函数的情况下生成自定义的静态成员函数(与常规函数一样,并且与非函数不同)静态成员函数,可以获取其地址并用于函数指针)。 需要一个函数指针模板参数将回调函数的知识嵌入到静态成员函数中。 The technique is demonstrated here.

答案 4 :(得分:2)

通常使用指针模板参数来利用SFINAE。如果你有两个类似的重载你不能使用std::enable_if默认参数,这会特别有用,因为它们会导致重新定义错误。

此代码会导致重新定义错误:

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo (T x)
{
    cout << "integral"; 
}

template <typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void foo (T x)
{
    cout << "floating";
}

但是这段代码利用了有效std::enable_if_t构造默认崩溃到void这一事实,很好:

                      // This will become void* = nullptr
template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo (T x)
{
    cout << "integral"; 
}

template <typename T, std::enable_if_t<std::is_floating_point<T>::value>* = nullptr>
void foo (T x)
{
    cout << "floating";
}