如何使c++模板化函数与返回类型无关,以便将来专门化

How to make a C++ templated function agnostic to the return type for future specialization

本文关键字:专门化 将来 返回类型 c++ 何使 函数      更新时间:2023-10-16

我想有一个通用的模板函数声明,我不知道(已经)返回类型,类似于:

**template <class T> auto getIds() noexcept -> std::vector<Any>;**

然后可以用几个输入类型和一个基于它的返回类型对函数进行专门化:

template <> auto getIds<MyClass>() noexcept -> std::vector<decltype(MyClass::id)>
{
    // implementation here.
}

最后调用它而不设置返回值:

auto val = getIds<MyClass>();

这可能吗?如何?

指出:

我想避免的是必须在调用函数中手动设置Id类型:

auto val = getIds<MyClass, decltype(MyClass::id)>(); // Ugly

我也抛弃了任何(非基于模板的)解决方案,比如从RootMyClass扩展所有类型。不是这些解决方案不好,而是它们没有抓住问题的关键。

试着更清楚一点:

如果我写

class MyClass { public: int id1=4;};
template <class T, class Id> auto getIds() -> Id;
template <> auto getIds<MyClass, decltype(MyClass::id1)>() -> decltype(MyClass::id1)
{
    return 1;
}
auto main() -> int
{
    getIds<MyClass>(); // Do not compile
    getIds<MyClass, decltype(MyClass::id1)>(); // Compile but ugly
}

我希望返回类型是隐式的,但是我没有找到通过专门化实现这一点的方法:

template <class T> getIds() noexcept -> WHICH TYPE?;

不幸的是,您不能在专门化中更改返回类型。你能做的就是在不同的重载中改变返回类型。很明显。此外,函数模板特化要比函数重载复杂得多,所以我们就这样做吧。

引入一个空的类型包装器,如:

template <typename T> struct wrapper { };

并将默认实现转发给它(我假设这里是c++ 14,否则您可以将其包装在decltype()中,并在末尾返回):

template <typename T>
auto getIds() { return getIds(wrapper<T>{}); }

将泛型版本声明为:

template <typename T>
void getIds(wrapper<T> );

不要定义它。然后,每当有人试图做:

auto ids = getIds<X>();

如果没有重载,编译失败,因为不能从void赋值。然后,您可以在您认为合适的时候重载:

std::vector<decltype(MyClass::id)> getIds(wrapper<MyClass> )
{ ... }

最后一个例子:

#include <iostream>
#include <vector>
template <typename T> struct wrapper { };
template <typename T>
auto getIds() -> decltype(getIds(wrapper<T>{}))
{
    return getIds(wrapper<T>{});
}
template <typename T>
void getIds(wrapper<T> ) { }
struct MyClass {
    int id;
};
std::vector<decltype(MyClass::id)> getIds(wrapper<MyClass> )
{
    return {1, 2, 3};
}
int main()
{
    for (auto id : getIds<MyClass>()) {
        std::cout << id << " ";
    }
}

这实际上非常类似于Haskell类型类,并且令人惊讶地工作。在实际使用中,我会使用函数来允许部分专门化。

#include <iostream>
template<typename T> 
decltype(T::x) getX(T const& t) { return; }
class A { public: int x; A(int x):x(x){} };
template<> int getX<A>(A const& a) {
    return a.x;
}
class B { public: std::string x; B(std::string x):x(std::move(x)){}  };
template<> std::string getX<B>(B const& b) {
    return b.x;
}
int main() {
    A a(42);
    B b("43");
    std::cout << getX(a) << std::endl;
    std::cout << getX(b) << std::endl;
}

可以看到,每个专门化都必须(可以?)显式地提供返回类型。如果您愿意,可以使用decltype(A::x)(和B::x)分别)。


为了使它更像haskell,您可以期望在类型本身(基本上是一个类型族)中有一个类型标记:

template<typename T> 
typename T::TypeOfX getX(T const& t) { return; }

,因此:

class A {
    using TypeOfX = int;
    TypeOfX someComplexLogicToGetX();
};

为实际类型实例化类型的两种解决方案,除了一种从字段的类型获取,另一种从直接的"类型变量"获取。