【问题标题】:Is there any way to detect whether a function exists and can be used at compile time?有什么方法可以检测函数是否存在并且可以在编译时使用吗?
【发布时间】:2012-06-09 02:30:53
【问题描述】:

编辑:对我的问题的简短回答是,我对 SFINAE 可以做什么有一个错误的看法,它根本不检查函数体:does sfinae instantiates a function body?

我有一个类似的问题:Is it possible to write a template to check for a function's existence?

不同之处在于,我不仅要检查函数是否存在,还要知道它是否真的会通过 SFINAE。这是我要完成的一个示例:

struct A
{
    void FuncA() { std::cout << "A::FuncA" << std::endl; }
};

struct B
{
    void FuncA() { std::cout << "B::FuncA" << std::endl; }
    void FuncB() { std::cout << "B::FuncB" << std::endl; }
};

template<typename T>
struct Inter
{
    void FuncA() { t.FuncA(); }
    void FuncB() { t.FuncB(); }

    T t;
};

// Always takes some sort of Inter<T>.
template<typename InterType>
struct Final
{
    void CallFuncs()
    {
        // if( t.FuncA() exists and can be called )
            t.FuncA();

        // if( t.FuncB() exists and can be called )
            t.FuncB();
    }

    InterType t;
};

void DoEverything()
{
    Final<Inter<A>> finalA;
    Final<Inter<B>> finalB;

    finalA.CallFuncs();
    finalB.CallFuncs();
}

请注意,在 CallFuncs() 中,FuncA() 和 FuncB() 将始终存在,但根据 Inter 中使用的类型 T,它们可能无法编译。当我尝试在上面链接的问题中使用答案时,它似乎总是给我真实的,我猜是因为它只是检查函数是否存在,而不是它实际上可以编译(尽管我不能排除我没有搞砸什么...)

为了有条件地调用我认为我可以使用 enable_if 的函数:

template<typename InterType>
typename std::enable_if< ! /* how to determine if FuncA can be called? */>::type TryCallFuncA( InterType& i )
{
}
template<typename InterType>
typename std::enable_if</* how to determine if FuncA can be called? */>::type TryCallFuncA( InterType& i )
{
    i.FuncA();
}

template<typename InterType>
typename std::enable_if< ! /* how to determine if FuncB can be called? */>::type TryCallFuncB( InterType& i )
{
}
template<typename InterType>
typename std::enable_if</* how to determine if FuncB can be called? */>::type TryCallFuncB( InterType& i )
{
    i.FuncB();
}

template<typename InterType>
struct Final
{
    void CallFuncs()
    {
        TryCallFuncA(t);
        TryCallFuncB(t);
    }

    InterType t;
};

但我不确定是否有任何方法可以将布尔值传递给 enable_if。有什么办法可以做到这一点,还是需要回退到某种手动维护的类型特征来指示函数是否存在?

就可用的 C++11 功能集而言,我使用的是 MSVC 2010。

编辑:补充一点,在我的实际情况下,Inter 类的实现实际上是不透明的,我需要确定 Inter::FuncA/FuncB 是否会编译所以我不能只是冒泡子类型并检查它们是否存在函数。

【问题讨论】:

    标签: c++ templates metaprogramming


    【解决方案1】:

    我现在没有时间检查这个,但您可以添加 Final 的特化:template &lt;typename T&gt; struct Final&lt; Inner&lt;T&gt; &gt;;(这也有助于确保类型始终为 Inner。这样您就可以提取用于实例化Inter 的类型。

    现在第二个问题是如何使用SFINAE来检测成员函数是否存在。我相信这不应该太复杂(如果您不需要使这个通用):

    // Find out whether U has `void f()` member
    template <typename U>
    struct has_member_f {
        typedef char yes;
        struct no { char _[2]; };
        template<typename T, void (T::*)() = &T::f>
        static yes impl( T* );
        static no  impl(...);
    
        enum { value = sizeof( impl( static_cast<U*>(0) ) ) == sizeof(yes) };
    };
    

    您可能可以对其进行一些扩展以使其更通用,但我认为您不能将函数的名称设为通用。当然,您可以将其编写为生成has_member_##arg 并使用&amp;T:: arg 的宏。成员的类型可能更容易概括...

    另外,由于我不认为这可以通用,您可以直接在您的类型中使用 has_member 中的技巧:提供两个 callFuncA 重载,一个模板带有可选的第二个参数,带有您的签名希望并默认为转发呼叫的&amp;T::FuncA,另一个带有省略号的省略号。然后callFuncs 会调用callFuncAcallFuncB,SFINAE 会派发到货代或中午,你会得到你想要的行为。

    template<typename T>
    struct Final< Inter<T> >
    {
        template <typename U, void (U::*)() = &U::FuncA>
        void callFuncA( Inter<T>* x ) {
            x.FuncA();
        }
        void callFuncA(...) {}
    
        void CallFuncs() {
            callFuncA(&t);                 // Cannot pass nonPOD types through ...
            // Similarly TryCallFuncB(t);
        }
        Inter<T> t;
    };
    

    【讨论】:

    • 如果我没记错的话,C++03 中的默认参数和函数模板存在问题,这会使该解决方案仅在 C++11 中可用。你能确认/确认吗?
    • @MatthieuM.:正确,这是 C++11 唯一的解决方案。在 C++ 中,您不能为函数模板提供默认参数。这是一个非常好的功能,在新标准中已经被忽视了,并且在标准要求特定函数除非 X 为真,否则不应参与重载解决方案的许多情况下都需要。(即强制 SFINAE)。
    • 不幸的是,VS(2010 或 2012)不支持。为了理解解决方案,默认参数 &U::FuncA 不仅检查它是否存在,而且如果它被调用,它将正确编译(例如,提供给 Inter 的类型本身可能是一个模板类(例如 struct A)和 FuncA/FuncB 的实现取决于该类型)?
    • @Screndib:检测基于SFINAE,只能检查是否存在。如果该成员不可访问(它是私有的),则它不会是 substitution 错误,并且编译器将因错误而失败。现在,在这种情况下,诀窍是您不想检查Inter&lt;X&gt;,而是检查X(因为在您的情况下,Inter&lt;T&gt; 始终具有这两个功能,即使它们无法编译。现在如果X 本身就是模板或普通类的实例化并不重要,一旦模板被实例化,它就会变成一种类型,就像(或至少非常接近)手卷类一样。
    • ... 也就是说,如果您的类型是 Final&lt;Inter&lt;A&lt;X&gt;&gt;&gt;,此解决方案将提取 A&lt;X&gt; 块并在那里进行测试。可以递归应用相同的技巧,即您可以添加一个额外的级别以删除A 模板,提取X 并验证X::f 是否存在。
    猜你喜欢
    • 2022-01-12
    • 2013-04-02
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    • 1970-01-01
    • 2018-01-31
    • 2013-03-20
    • 2015-08-19
    相关资源
    最近更新 更多