【问题标题】:Function template argument deduction with user-defined conversion operator使用用户定义的转换运算符推导函数模板参数
【发布时间】:2019-10-14 19:52:43
【问题描述】:

假设我有一个包装字符串文字的类:

template <size_t N>
class literal {
public:
    constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}

    constexpr const char * c_str() const { return wrapped_; }
    constexpr size_t size() const { return N; }

private:
    const char (&wrapped_)[N+1];
};

template <size_t N>
literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }

现在,我希望这种包装字符串类型的实例可以隐式转换回const char[N],在某种程度上我仍然可以访问它的大小。我希望能够做类似的事情:

template <size_t N>
void foo(const char(&s)[N]) {
    std::cout << N << ": " << s << std::endl;
}

int main() {
    constexpr auto s = make_literal("testing");
    foo(s);
}

我的目标是为foo() 定义一个函数,它可以接受实际的字符串文字以及包装的文字。我尝试将用户定义的转换运算符添加到类定义中:

using arr_t = char[N+1];    
constexpr operator const arr_t&() const { return wrapped_; }

但这给了我以下的叮当声:

候选模板被忽略:无法将 'const char [N]' 与 'const literal' 匹配

如果我将对foo() 的调用更改为以下内容,则可以:

foo((const char(&)[8])s);

...这意味着转换运算符有效,但不是在模板参数推导的上下文中。有没有什么方法可以在不定义 foo() 的情况下完成这项工作以获取包装的文字?

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    您遇到的问题是模板从不转换参数。既然你给它一个const literal&lt;7&gt;,那就是它的全部工作。

    解决这个问题的简单方法是添加一个重载,然后在重载中进行强制转换以调用您的字符串文字版本。应该是这样的

    template <size_t N>
    void foo(const literal<N> &lit) {
        foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
    }
    

    给你一个完整的例子

    template <size_t N>
    class literal {
    public:
        constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
    
        constexpr const char * c_str() const { return wrapped_; }
        constexpr size_t size() const { return N; }
        using arr_t = const char[N+1];    // <- Add const here since literals are const char[N]
        constexpr operator const arr_t&() const { return wrapped_; }
    
    private:
        const char (&wrapped_)[N+1];
    };
    
    template <size_t N>
    constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
    
    template <size_t N>
    void foo(const char(&s)[N]) {
        std::cout << N << ": " << s << std::endl;
    }
    
    template <size_t N>
    void foo(const literal<N> &lit) {
        foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
    }
    
    int main() {
        constexpr auto s = make_literal("testing");
        foo(s);
    }
    

    是的,您正在添加重载,但不必复制所有重要代码。


    如果您可以使用 C++17 并且不介意一点间接性,您可以使用 std::string_view 并为 literal 提供 operator std::string_view 的一个函数来完成所有这些操作。看起来像

    template <size_t N>
    class literal {
    public:
        constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
    
        constexpr const char * c_str() const { return wrapped_; }
        constexpr size_t size() const { return N; }
        using arr_t = const char[N+1];    
        constexpr operator std::string_view() const { return wrapped_; }
    
    private:
        const char (&wrapped_)[N+1];
    };
    
    template <size_t N>
    constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
    
    void foo(std::string_view s) {
        std::cout << s.size() << ": " << s << std::endl;
    }
    
    
    int main() {
        constexpr auto s = make_literal("testing");
        foo(s);
        foo("testing");
    }
    

    【讨论】:

    • 是的,我知道我可以走那条路。我也可以在采用包装文字的版本中定义实际逻辑,然后使用 make_literal() 从采用 const char(&amp;)[N] 的版本转发到该重载,这可以说比这更简单。我只是希望有一种方法可以使一个函数定义工作,但没有意识到模板参数推导不考虑转换。我想我无论如何都需要两个重载。
    • @JohnDrouhard 根据你实际在做什么,以及你可以使用什么标准,C++17 提供std::string_view。我已将其添加到答案中
    • 太棒了!我总是忘记std::string_view(并且它有 constexpr 一切)。我还没有访问 C++17,但我很快就会,我会记住这一点。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2020-12-14
    • 2015-07-22
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-12
    • 1970-01-01
    • 2019-04-17
    相关资源
    最近更新 更多