【问题标题】:Template function specialization for template class模板类的模板函数特化
【发布时间】:2015-04-20 18:10:07
【问题描述】:

是否可以在 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&lt;...&gt;() 的调用将是模棱两可的 (see):

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

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

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

【问题讨论】:

  • 不,我们仍然不能部分专门化函数模板。是的,有一些解决方法。
  • 我更新了我的答案以将其应用于您的全新问题。不过,希望不会有任何其他变化。

标签: c++ templates c++11 c++14 template-specialization


【解决方案1】:

有一种解决方法,可以为您提供如下内容:不要专门化 - 重载:

#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&lt;T&gt;&amp; data) 不是特化 - 它是完全“不同的”Get(),对于任何 T 类型的参数都调用 Data&lt;T&gt;

Here你可以看到工作样本。


编辑

我看到你完全改变了你的问题。但是,我仍然会尝试回答。对于缺少部分函数专门化,有一个标准的解决方法 - 使用对结构/类的委托。

这是你需要的:

#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

Working sample can be found here.


如果您不喜欢该语法,可以将其缩短一点,将静态函数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&lt;Data&lt;int&gt;&gt;(),而不是GetImpl&lt;Data&lt;int&gt;&gt;::Get()
  • 不,你不能叫它Get&lt;Data&lt;int&gt;&gt;(),但你可以叫它Get&lt;Data&lt;int&gt;&gt;()()。请更新答案。
  • “不,你不能称之为 Get>()”,呃……you can,@KostyaBazhanov fyi
  • @piotr-s 谢谢,C++14 真的很棒! auto 救援!
  • @PiotrS。在这种情况下,可能是最准确的解决方案。没想到,问题改了太多次了。我建议将此作为答案发布,这是我认为最正确的答案。
【解决方案2】:

正如 Columbo 在他的评论中提到的,您应该应用标准的解决方法,因为缺少对函数的部分专业化支持:委托给部分专业化的类:

template <typename T>
struct GetImpl;

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

现在在struct GetImpl&lt;T&gt; { static T Do(); } 上使用部分特化而不是Get&lt;T&gt;()

【讨论】:

    【解决方案3】:

    但是编译器不可能区分Get&lt;Data&lt;int&gt;&gt;Get&lt;Data&lt;Data&lt;int&gt;&gt;&gt;

    这不是不可能的。如果这是您需要做的事情,我们可以添加单独的重载:

    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&lt;Data&lt;int&gt;&gt; 的调用将调用通用Get(const T&amp;)。或者,如果您希望该案例根本不编译:

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

    所以重载给了你很多选择。专业化不给你任何东西,因为无论如何它都是不允许的。

    【讨论】:

    • 对不起,我发现问题并不清楚,关于歧义的陈述是错误的,因为Length 的函数签名实际上是不同的。请查看更新版本。
    • @KostyaBazhanov 老兄,你刚刚完全改变了问题。
    • 是的,第一次尝试时过于简化了:)
    【解决方案4】:

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

    #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

    【讨论】:

    • 酷!迄今为止最好的答案!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-27
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多