【问题标题】:templates, decltype and non-classtypes模板、decltype 和非类类型
【发布时间】:2013-05-12 08:43:19
【问题描述】:

我有一个这样的函数定义

template <typename T>
auto print(T t) -> decltype(t.print()) {
    return t.print();
}

想法是参数必须是T 类型并且必须具有print 函数。这个print 函数可以返回任何东西,解释了decltype 的需要。所以例如你可以这样做:

struct Foo
{
    int print()
    {
        return 42;
    }
};

struct Bar
{
    std::string print()
    {
        return "The answer...";
    }
};

...

std::cout << print(Foo()) << std::endl;    
std::cout << print(Bar()) << std::endl;
/* outputs: 
42
The answer...
*/

我读到模板不能进行运行时实例化,您可以让类派生自基类,然后确定它们的类型以查看要使用的模板参数。但是,我将如何为 non-class 类型执行此操作?我们的想法是能够:

template <typename T>
T print(T t) {
    return t;
}

也是如此,但这给了我模棱两可的过载错误。排位赛不起作用,即print&lt;Foo&gt;。另一个问题是,如果我有一个函子会怎样:

struct Foo
{
  virtual int print();
  operator int() const
  {
    return 42;
  }
};

它现在如何决定?

所以我的问题是,是否有可能用模板解决所有这些歧义,还是我必须编写一堆冗余代码?

测试

我逐渐添加了测试,从下面复制/粘贴每个已编辑的解决方案。结果如下:

具有以下类:

struct Foo
{
    int print()
    {
        return 42;
    }

    operator int() const
    {
        return 32;
    }
};

struct Bar
{
    std::string print()
    {
        return "The answer...";
    }

    operator int() const
    {
        return (int)Foo();
    }
};

struct Baz
{
    operator std::string() const
    {
        return std::string("The answer...");
    }
};

以及以下测试输出:

std::cout << print(Foo()) << std::endl;    
std::cout << print(Bar()) << std::endl;
std::cout << print(42) << std::endl;
std::cout << print((int)Foo()) << std::endl;
std::cout << print("The answer...") << std::endl;
std::cout << print(std::string("The answer...")) << std::endl;
std::cout << print((int)Bar()) << std::endl;
std::cout << print((std::string)Baz()) << std::endl;

都正确输出:

42
The answer...
42
32
The answer...
The answer...
32
The answer...

【问题讨论】:

  • 这是SFINAE 的案例。简而言之:您为那些具有T::print() 的类型定义您的第一个实现,而为所有其他情况定义第二个实现。另一种选择是使用您想要支持但没有打印功能的所有类型手动重载您的函数,例如:int print(int);

标签: c++ templates c++11 decltype


【解决方案1】:

您可以采用以下方法,如果存在print()成员函数,则在输入上调用该成员函数,否则将返回输入本身:

namespace detail
{
    template<typename T, typename = void>
    struct print_helper
    {
        static T print(T t) {
            return t;
        }
    };

    template<typename T>
    struct print_helper<T, decltype(std::declval<T>().print(), (void)0)>
    {
        static auto print(T t) -> decltype(t.print()) {
            return t.print();
        }
    };
}

template<typename T>
auto print(T t) -> decltype(detail::print_helper<T>::print(t))
{
    return detail::print_helper<T>::print(t);
}

这是live example

【讨论】:

  • 我认为你的条件颠倒了。
  • @Xeo:是的,无论如何我都会编辑——我不喜欢这个版本;)
  • 现在我已经看过这个结构几次,但仍然不知道它是如何被调用的,它是如何/为什么工作的以及如何自己编写它:你能给我指点一些不错的文档吗或者告诉我它是否有名字? (这不是类型特征,但它 SINFAE,不是吗?)
  • @leemes:是的,它被称为表达式SFINAE。您可以在 this Q&A 中找到有关它的更多信息
【解决方案2】:

使用手动重载为您要打印的每种类型的简单解决方案直接

定义你的第一个实现,它调用T::print()。使用重载为所有没有此功能的类型指定替代实现。我不推荐这种解决方案,但它很容易理解。

template<typename T>
auto print(T t) -> decltype(t.print()) {
    return t.print();
}

int print(int t) {
    return t;
}
std::string print(std::string t) {
    return t;
}
// ... and so on, for each type you want to support ...

使用 SFINAE 的更高级解决方案,当且仅当 T::print() 存在时自动使用它:

首先,定义一个特征,它可以决定你的类型是否有函数print()。基本上,这个特征继承自 std::true_typestd::false_type,这取决于在某个帮助程序类 (_test_print) 中做出的决定。然后,在enable_if 编译时决策中使用此类型特征,该决策只定义了两种情况中的一种,并隐藏了另一种情况(所以这不是重载)。

// Type trait "has_print" which checks if T::print() is available:
struct _test_print {
    template<class T> static auto test(T* p) -> decltype(p->print(), std::true_type());
    template<class>   static auto test(...)  -> std::false_type;
};
template<class T> struct has_print : public decltype(_test_print::test<T>(0)) {};


// Definition of print(T) if T has T::print():
template<typename T>
auto print(T t) -> typename std::enable_if<has_print<T>::value, decltype(t.print())>::type {
    return t.print();
}

// Definition of print(T) if T doesn't have T::print():
template<typename T>
auto print(T t) -> typename std::enable_if<!has_print<T>::value, T>::type {
    return t;
}

看看live demo

【讨论】:

  • 不得不接受这个。这适用于非基本类型,如 strings 和 const char
  • 对不起,我把&lt;class T&gt;&lt;typename T&gt;混在一起了;这是因为我从我的一个项目中复制粘贴了检查类型特征。如果你还不知道,那也是一样的。 ;)
  • @remyabel:(我确实更新了我的答案,以便它也适用于非基本类型。诚然,第一个版本很糟糕)
  • 我添加了一些测试输出,所以我还没有选择答案。我很好奇有什么细微的差异,并且输出显示。
  • @remyabel Andy 和我的回答基本相同,只是使用了不同的语言结构。我的使用 SFINAE,他使用模板专业化。它们导致完全相同的结果。它们只是在内部看起来和工作方式不同;)
猜你喜欢
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-05
  • 2018-02-12
  • 1970-01-01
  • 1970-01-01
  • 2021-03-18
相关资源
最近更新 更多