模板类的模板函数特化

9
在C++11/14中是否可以编写类似于这样的内容?
#include <iostream>
#include <vector>

template <typename T>
T Get();

template <typename T>
struct Data {
    std::vector<T> data;
};

template <>
template <typename T>
Data<T> Get<Data<T>>() {
    return Data<T>{{T{}, T{}}};
}

template <>
template <typename T>
std::vector<T> Get<std::vector<T>>() {
    return std::vector<T>(3);
}

int main() {
    std::cout << Get<Data<int>>().data.size() << std::endl;  // expected output is 2
    std::cout << Get<std::vector<int>>().size() << std::endl; // expected output is 3
    return 0;
}

在这种情况下,重载并不能解决问题,因为对 Get<...>() 的调用将会是模棱两可的(参见):

template <typename T>
Data<T> Get() {
    return Data<T>{{T{}, T{}}};
}

template <typename T>
std::vector<T> Get() {
    return std::vector<T>(3);
}

欢迎提供任何关于如何克服这个问题的指导。


4
不,我们仍然不能对函数模板进行部分特化。是的,有一些解决方法。 - Columbo
我更新了我的答案以适应你的全新问题。希望不会有其他变化。 - Mateusz Grzejek
4个回答

7
有一个解决办法,可以让您达到以下效果:不要专门化 - 重载:
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename T>
size_t Get(const T& data)
{
    return 444;
}

template <typename T>
struct Data
{
    std::vector<T> data;
};

template <typename T>
size_t Get(const Data<T>& data) {
    return data.data.size();
}

int main() {
    std::cout << Get<>(0) << std::endl;  // expected output is 444
    std::cout << Get<>(Data<int>{}) << std::endl;  // expected output is 0
    return 0;
}

输出:

444
0

请注意,size_t Get(const Data<T>& data)不是一种特化 - 它完全是针对任何T类型的Data<T>参数调用的不同Get()

这里可以看到工作示例。


编辑

我看到你完全改变了问题。然而,我仍然会尝试回答它。有一种标准的解决缺少部分函数特化的方法 - 使用委托给结构体/类。

以下是您需要的内容:

#include <iostream>
#include <vector>

using namespace std;

template <typename T>
struct GetImpl;

template <typename T>
struct Data {
    std::vector<T> data;
};

template <typename T>
struct GetImpl< Data<T> >
{
    static Data<T> Get() {
        return Data<T>{ {T{}, T{}} };
    };
};

template <typename T>
struct GetImpl< std::vector<T> >
{
    static std::vector<T> Get() {
        return std::vector<T>(3);
    };
};

int main() {
    std::cout << GetImpl< Data<int> >::Get().data.size() << std::endl;  // expected output is 2
    std::cout << GetImpl< std::vector<int> >::Get().size() << std::endl; // expected output is 3
    return 0;
}

输出:

2
3

这里可以找到工作样例。


如果您不喜欢语法,可以通过将静态函数 Get() 更改为函数调用运算符来使其缩短:

template <typename T>
struct Get< Data<T> >
{
    Data<T> operator()() {
        return Data<T>{ {T{}, T{}} };
    };
};

template <typename T>
struct Get< std::vector<T> >
{
    std::vector<T> operator()() {
        return std::vector<T>(3);
    };
};

接着:

Get< Data<int> >()().data.size();
Get< std::vector<int> >()().size();

你只需要增加两个额外的字符 - ()。这是我能想到的最简短的解决方案。

有没有可能保持接口不变?这样我就可以调用Get<Data<int>>(),而不是GetImpl<Data<int>>::Get() - Kostya
不,你不能称之为 Get<Data<int>>(),但可以使用 Get<Data<int>>()()。请参见更新后的答案。 - Mateusz Grzejek
3
不,你不能称其为"Get<Data<int>>()"。对了,@KostyaBazhanov,FYI,你可以在此链接中查看示例:http://coliru.stacked-crooked.com/a/b7b5421ea28e05b7。 - Piotr Skotnicki
@piotr-s 谢谢,C++14 的使用真的很棒!auto来拯救! - Kostya
@PiotrS。在这种情况下,可能是最准确的解决方案。我没有想到过,问题已经改变了太多次。我建议将此作为答案发布,我认为这是最正确的答案。 - Mateusz Grzejek
显示剩余3条评论

5

正如Columbo在他的评论中提到的那样,您应该采用缺乏函数部分特化支持的标准解决方法:将其委托给一个部分特化的类:

template <typename T>
struct GetImpl;

template <typename T>
T Get() { return GetImpl<T>::Do(); }

现在,我们需要对struct GetImpl<T> { static T Do(); }进行部分特化,而不是使用Get<T>()

3
但编译器无法区分Get<Data<int>>Get<Data<Data<int>>>
这并非不可能。如果您需要执行此操作,我们可以添加单独的重载:
template <typename T>
size_t Get(const Data<T>& data);

template <typename T>
size_t Get(const Data<Data<T>>& data); // preferred for Data<Data<int>>

或者,如果您想仅对非嵌套情况进行重载,我们可以添加一个类型特征并使用SFINAE:

template <typename T> struct is_data : std::false_type { };
template <typename T> struct is_data<Data<T>> : std::true_type { };

template <typename T>
enable_if_t<!is_data<T>::value, size_t>
Get(const Data<T>& data);

这样,使用 Data<Data<int>> 的调用将会调用通用的 Get(const T&) 方法。或者,如果您希望这种情况根本不编译:

template <typename T>
size_t Get(const Data<T>& data) {
    static_assert(!is_data<T>::value, "disallowed");
    ...
}

因此,重载为您提供了许多选项。而特化则没有任何选项,因为它无论如何都被禁止了。


抱歉,我发现问题不够清楚,有关模糊性的陈述是错误的,因为 Length 函数签名实际上是不同的。请查看更新的版本。 - Kostya
2
@KostyaBazhanov 伙计,你刚刚完全改变了问题。 - Barry
第一次尝试过于简化了 :) - Kostya

2

通过委派给结构体的方式,您可以实现更通用的方法:您可以使用结构体来检查容器类型和内部类型,如下所示:

#include <iostream>
#include <vector>

template <typename T>
struct Data {
    std::vector<T> data;
};

template <template <typename...> class Container, typename>
struct get_inner;

template <template <typename...> class Container, typename T>
struct get_inner<Container, Container<T>>
{
    typedef T type;
};




template <typename T, typename U = typename get_inner<Data, T>::type>
Data<U> Get() {
    return Data<U>{ {U{}, U{}} };
}

template <typename T, typename U = typename  get_inner<std::vector, T>::type>
std::vector<U> Get() {
    return std::vector<U>(3);
}

int main() {
    std::cout << Get<Data<int>>().data.size() << std::endl;  // expected output is 2
    std::cout << Get<std::vector<int>>().size() << std::endl; // expected output is 3
    return 0;
}

http://coliru.stacked-crooked.com/a/90b55767911eff0e


棒极了!到目前为止最好的答案! - Kostya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接