【问题标题】:Using std::enable_if to declare a parenthesis operator with variadic template args使用 std::enable_if 声明带有可变参数模板参数的括号运算符
【发布时间】:2012-11-27 23:15:57
【问题描述】:

我有以下smart_ptr 类。

template <typename T>
class smart_ptr
{
public:
    // ... removed other member functions for simplicity
    T* get() { return ptr; }

    template <typename... Args>
    decltype(T::template operator ()(Args()...)) operator ()(Args... args) const
    {
        return (*get()).operator ()(args...);
    }

private:
    T* ptr;
};

但是,当我将 smart_ptr 类用于没有 operator () 的类型 T 时,它无法编译(这是正确的)。

我想要的是只有当 T 有operator () 时才使用std::enable_if 来启用成员函数。当我开始这样做时,它变得非常模糊和复杂,因为我正在考虑使用 SFINAE 来检测 T 是否有operator (),然后将它与std::enable_if 结合起来得到我想要的。但是,我在创建 SFINAE 时迷失了方向,该 SFINAE 能够检测 一个以 operator () 为模板的可变参数(即 不是Is it possible to write a template to check for a function's existence? 的副本)。

有人可以帮忙吗?或者给出另一个(也许更简单的)解决方案?

附言它必须在 GCC 4.5.3 上工作。

编辑:为了完整起见,我已经用 GCC 4.5.3 的有效解决方案回答了我自己的问题。

【问题讨论】:

  • 具体来说,检查my answer,它显示了如何使用尾随返回类型轻松完成:auto operator()(Args... args) -&gt; decltype((*get())(args...)){ ... }。不过,问题是:为什么限制为operator()?如果T定义了一个函数指针的转换,你可以得到所谓的surrogate call functions,虽然它们可能不常见,但最好是通用的(而且看起来更整洁)。
  • Xeo:它是如何复制的?这是一个带有可变参数模板的函数,而不是几年前我已经知道的直接函数!
  • '它无法编译(而且是正确的)'。嗯,编译器不应该将类模板成员的实例化推迟到它们被使用的地步吗?这意味着它应该不会编译失败,除非您实际上尝试使用 operator() 您以某种方式强制完成模板实例化?
  • @Zach:嗯,你确实知道 4.5 通常不支持可变参数模板,对吧?有什么原因不能更新编译器?
  • @Xeo:这个问题明确针对 GCC 4.5.3。如果它不可能,那么回答并说不可能。

标签: c++ templates gcc c++11 sfinae


【解决方案1】:

已编辑:与原始答案完全不同,没有给出真正的解决方案。

此代码在使用 gcc 4.5.1 的 ideone 上编译。这是我能做的最好的了。

#include <array>
#include <iostream>

using namespace std;

template<typename T>
struct has_property
{
    struct UnmentionableType { UnmentionableType() {} };

    //test if type has operator() (...)
    template<typename U, typename A1, typename A2, typename A3, typename A4, typename A5>
    static auto ftest(U* t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) -> decltype((*t)(a1, a2, a3, a4, a5), char(0));
    static std::array<char, 2> ftest(...);

public:
    static const bool value = sizeof(ftest((T*)0, UnmentionableType(), UnmentionableType(), UnmentionableType(), UnmentionableType(), UnmentionableType())) == 1;
};

class c1
{
public:
    int operator() () {  return 0; }
};

class c2
{
public:
    void operator() (int) { }
};

class c3
{
public:
    template<typename... Args>
    void operator() (Args... a) { }
};

class c4
{
public:
    template<typename T>
    void operator() (T t) { }
};

int main()
{
    cout<<boolalpha<<has_property<c1>::value<<endl;
    cout<<boolalpha<<has_property<c2>::value<<endl;
    cout<<boolalpha<<has_property<c3>::value<<endl;
    cout<<boolalpha<<has_property<c4>::value<<endl;
}

输出:

false
false
true
false

注意事项:

1) 根据要求,它仅在可变参数模板化 operator() 上触发(嗯,几乎)

2) 这不是一个“完美”的解决方案,但对于实际使用来说应该足够了。

很遗憾 gcc 4.5.1 不能扩展参数包;我怀疑这可能对你有用:

template<typename U, typename... A>
static auto ftest(U* t, A... a) -> decltype((*t)(a...), char(0));

我仍然看不到一种方法来实际测试函数的真正可变性,而不是只传递 5 个(或更多)随机类型参数。

使用 with_enable if - 除了部分类专业化,我没有看到任何其他解决方案:

//non-specialized
template<typename T, typename E=void>
class container
{
    T* ptr;

public:
    T* get() { return ptr; }
};

//specialization for class with appropriate operator()
template<typename T>
class container<T, typename std::enable_if<has_property<T>::value, void>::type>
{
    T* ptr;

public:
    T* get() { return ptr; }

    template <typename... Args>
    decltype(T::template operator ()(Args()...)) operator ()(Args... args) const
    {
        return (*get()).operator ()(args...);
    }
};

【讨论】:

  • 我的问题是专门''对于可变参数模板 operator()''
  • 更不用说 GCC 4.5.3
  • 很公平。我已将我的答案编辑为(希望)有用的东西。
  • 感谢您的回答。这非常接近我想要的。您是否还可以将 enable_if 与您的解决方案一起使用来演示我如何实现我所追求的目标?特别是 enable_if 的第二个模板参数——我不知道要传递什么而不让编译器在未声明 operator () 的类型 T 上失败。
  • 鉴于您现有的代码,除了类模板专业化之外,我没有看到任何其他解决方案,这将涉及为这两种情况重写整个类或继承;这是可以接受的吗?我认为您不能对 operator() 使用函数模板特化的原因是它没有固定的返回类型,所以我看不到如何根据签名进行重载。也许有解决办法。
【解决方案2】:

最后,我找到了 GCC 4.5.3 的解决方法!

template <typename T>
class smart_ptr
{
public:
    // ... removed other member functions for simplicity
    T* get() const { return ptr; }

private:
    template <typename... Args>
    struct opparen_constret
    {
        typedef decltype(std::declval<T const>()(std::declval<Args>()...)) type;
    };

    template <typename... Args>
    struct opparen_ret
    {
        typedef decltype(std::declval<T>()(std::declval<Args>()...)) type;
    };

public:
    template <typename... Args>
    typename opparen_constret<Args...>::type operator ()(Args... args) const
    {
        return (*get())(args...);
    }

    template <typename... Args>
    typename opparen_ret<Args...>::type operator ()(Args... args)
    {
        return (*get())(args...);
    }

private:
    T* ptr;
};

【讨论】:

  • 如果你尝试实例化一个没有 operator() 的类型,你实际上可以编译它吗?您似乎没有后备案例(无法在 ideone 上编译)。
  • @AndreiTita:因为有 SFINAE,所以不需要回退。
  • 同意。由于其他一些代码(我的错),它没有编译。我现在明白这是如何工作的。挺好的。
猜你喜欢
  • 1970-01-01
  • 2012-06-18
  • 2016-03-15
  • 2017-05-29
  • 2020-04-22
  • 1970-01-01
  • 2014-08-30
  • 1970-01-01
  • 2011-12-13
相关资源
最近更新 更多