【问题标题】:Implicit conversion not happening in class templates类模板中未发生隐式转换
【发布时间】:2018-06-24 08:44:13
【问题描述】:

我正在编写一个简单的基于 std::array 的字符串类,如下所示。预计该类以非常有限的方式表现得像 std::string,但所有数据都存储在堆栈上。此外,它几乎没有从 const char* 和 std::string 构造的构造函数。下面是代码:

#ifndef SSTRING_H
#define SSTRING_H

#include <algorithm>
#include <array>
#include <cstring>
#include <iterator>
#include <ostream>


// friend operator declarations
template <typename CharT, std::size_t SizeT>
class basic_sstring;
template <typename CharT, std::size_t SizeT>
constexpr bool operator == (const basic_sstring<CharT, SizeT>&, const basic_sstring<CharT, SizeT>&);
template <typename CharT, std::size_t SizeT>
constexpr bool operator != (const basic_sstring<CharT, SizeT>&, const basic_sstring<CharT, SizeT>&);
template <typename CharT, std::size_t SizeT>
constexpr basic_sstring<CharT, SizeT> operator + (const basic_sstring<CharT, SizeT>&,
                                                  const basic_sstring<CharT, SizeT>&);
template <typename CharT, std::size_t SizeT>
std::basic_ostream<CharT>& operator << (std::basic_ostream<CharT>&, const basic_sstring<CharT, SizeT>&);


// basic_sstring class template
template <typename CharT, std::size_t SizeT = 512>
class basic_sstring
{
public:
    typedef CharT value_type;

    // constructor for empty basic_sstring
    constexpr basic_sstring() : _size(0) 
    {
        _data[_size] = static_cast<CharT>(0);
    }

    // constructor for const char* with a size specified
    constexpr basic_sstring(const CharT* data_, std::size_t size_)
        : _size(size_)
    {
        std::memcpy(&_data[0], data_, _size*sizeof(CharT));
        _data[_size] = static_cast<CharT>(0);
    }

     // constructor for const char* which is null terminated
    constexpr basic_sstring(const CharT* data_)
        : _size(std::char_traits<CharT>::length(data_))
    {
        std::memcpy(&_data[0], data_, _size*sizeof(CharT));
        _data[_size] = static_cast<CharT>(0);
    }

    // constructor for std::string
    constexpr basic_sstring(const std::basic_string<CharT>& str_)
        : _size(str_.size())
    {
        std::memcpy(&_data[0], str_.c_str(), _size*sizeof(CharT));
        _data[_size] = static_cast<CharT>(0);
    }

    // default copy constructor
    // default move constructor

    // operators

    // default copy assigment operator
    // default move assigment operator

    // comparison operators
    constexpr friend bool operator == <> (const basic_sstring<CharT, SizeT>&, const basic_sstring<CharT, SizeT>&);
    constexpr friend bool operator != <>(const basic_sstring<CharT, SizeT>&, const basic_sstring<CharT, SizeT>&);

    // + operator
    constexpr friend basic_sstring<CharT, SizeT> operator + <> (const basic_sstring<CharT, SizeT>&,
                                                                const basic_sstring<CharT, SizeT>&);

    // += operator
    constexpr basic_sstring<CharT, SizeT> operator += (const basic_sstring<CharT, SizeT>& other_)
    {
        return *this + other_;
    }

    // << operator
    friend std::basic_ostream<CharT>& operator << <> (std::basic_ostream<CharT>&,
                                                      const basic_sstring<CharT, SizeT>&);

    // reference to internal buffer
    constexpr const CharT* data() const { return &_data[0]; };

    // capacity
    constexpr std::size_t capacity() { return SizeT; }

    // size
    constexpr std::size_t size() { return _size; };

private:

    std::array<CharT, SizeT>        _data;
    std::size_t                    _size;
};

// == operator for basic_sstring
template <typename CharT, std::size_t SizeT>
constexpr bool operator == (const basic_sstring<CharT, SizeT>& first_,
                            const basic_sstring<CharT, SizeT>& second_)
{
    return first_._size == second_._size && first_._data == second_._data;
}

// != operator for basic_sstring
template <typename CharT, std::size_t SizeT>
constexpr bool operator != (const basic_sstring<CharT, SizeT>& first_,
                            const basic_sstring<CharT, SizeT>& second_)
{
    return !(first_ == second_);
}

// + operator for basic_sstring
template <typename CharT, std::size_t SizeT>
constexpr basic_sstring<CharT, SizeT> operator + (const basic_sstring<CharT, SizeT>& first_,
                                                  const basic_sstring<CharT, SizeT>& second_)
{
    auto result = first_;
    std::memcpy(&result._data[result._size], &second_._data[0], second_._size*sizeof(CharT));
    result._size += second_._size;
    result._data[result._size] = static_cast<CharT>(0);

    return result;
}

// << operator for basic_sstring
template <typename CharT, std::size_t SizeT>
std::basic_ostream<CharT>& operator << (std::basic_ostream<CharT>& out,
                                        const basic_sstring<CharT, SizeT>& sstr_)
{
    std::copy(sstr_._data.begin(), sstr_._data.begin() + sstr_._size, std::ostreambuf_iterator<char>(out));
    return out;
}

typedef basic_sstring<char> sstring;

#endif

但由于某种原因,当我使用它时,似乎没有发生来自 std::string 和 const char* 的隐式转换。我正在使用下面的代码来运行它。任何想法。

#include <iostream>
#include <string>

#include "SString.h"

int main()
{
    sstring a = "First";
    sstring b = a + " Second";
    sstring c = a + b + " Third";

    std::cout << "A = " << a << std::endl;
    std::cout << "B = " << b << std::endl;
    std::cout << "C = " << c << std::endl;
    return 0;
}

编辑

所以我遵循了下面 r3mus n0x 所描述的方法(以及由此导致的一些其他更改,例如必须在类本身中定义友元函数)。我在代码中还有一些错误。清除它们后,它工作正常。在下面添加工作字符串代码和测试代码以供参考。

#ifndef SSTRING_H
#define SSTRING_H

#include <algorithm>
#include <array>
#include <cstring>
#include <iterator>
#include <ostream>
#include <type_traits>


// friend operator declarations
template <typename CharT, std::size_t SizeN>
class basic_sstring;

template <typename CharT, std::size_t SizeN>
std::basic_ostream<CharT>& operator << (std::basic_ostream<CharT>&, const basic_sstring<CharT, SizeN>&);


// basic_sstring class template
template <typename CharT, std::size_t SizeN = 512>
class basic_sstring
{
public:
    typedef CharT value_type;

    // constructor for empty basic_sstring
    constexpr basic_sstring() : _size(0) 
    {
        _data[_size] = static_cast<CharT>(0);
    }

    // constructor for const char* with a size specified
    constexpr basic_sstring(const CharT* data_, std::size_t size_)
        : _size(size_)
    {
        std::memcpy(&_data[0], data_, _size*sizeof(CharT));
        _data[_size] = static_cast<CharT>(0);
    }

     // constructor for const char* which is null terminated
    constexpr basic_sstring(const CharT* data_)
        : _size(std::char_traits<CharT>::length(data_))
    {
        std::memcpy(&_data[0], data_, _size*sizeof(CharT));
        _data[_size] = static_cast<CharT>(0);
    }

    // constructor for std::string
    constexpr basic_sstring(const std::basic_string<CharT>& str_)
        : _size(str_.size())
    {
        std::memcpy(&_data[0], str_.c_str(), _size*sizeof(CharT));
        _data[_size] = static_cast<CharT>(0);
    }

    // default copy constructor
    // default move constructor

    // operators

    // default copy assigment operator
    // default move assigment operator

    // == operator
    template <typename T1, typename T2,
        typename String1 = decltype(basic_sstring(std::declval<T1>())),
        typename String2 = decltype(basic_sstring(std::declval<T2>()))>
    constexpr friend bool operator==(const T1 & first_, const T2 & second_)
    {
        static_assert(std::is_same<String1, String2>::value, "Incompatable type used to compare with sstring");
        auto first = String1(first_);
        auto second = String2(second_);
        return (first._size == second._size) && 
            (std::equal(first._data.begin(), first._data.begin() + first._size, second._data.begin()));
    }

    // != operator
    template <typename T1, typename T2,
        typename String1 = decltype(basic_sstring(std::declval<T1>())),
        typename String2 = decltype(basic_sstring(std::declval<T2>()))>
    constexpr friend bool operator != (const T1 & first_, const T2 & second_)
    {
        static_assert(std::is_same<String1, String2>::value, "Incompatable type used to compare with sstring");
        return !(first_ == second_);
    }

    // + operator
    template <typename T1, typename T2,
        typename String1 = decltype(basic_sstring(std::declval<T1>())),
        typename String2 = decltype(basic_sstring(std::declval<T2>()))>
    constexpr friend String1 operator + (const T1 & first_, const T2 & second_)
    {
        static_assert(std::is_same<String1, String2>::value, "Incompatable type used to add with sstring");
        auto result = String1(first_);
        auto second = String2(second_);
        std::memcpy(&result._data[result._size], &second._data[0], second._size*sizeof(typename String1::value_type));
        result._size += second._size;
        result._data[result._size] = static_cast<typename String1::value_type>(0);

        return result;
    }

    // += operator
    template <typename T, typename String = decltype(basic_sstring(std::declval<T>()))>
    constexpr basic_sstring<CharT, SizeN>& operator += (const T& other_)
    {
        static_assert(std::is_same<String, basic_sstring<CharT, SizeN> >::value,
                                             "Incompatable type used to add with sstring");
        auto other = String(other_);
        std::memcpy(&_data[_size], &other._data[0], other._size*sizeof(typename String::value_type));
        _size += other._size;
        _data[_size] = static_cast<typename String::value_type>(0);
        return *this;
    }

    // << operator
    friend std::basic_ostream<CharT>& operator << <> (std::basic_ostream<CharT>&,
                                                      const basic_sstring<CharT, SizeN>&);

    // reference to internal buffer
    constexpr const CharT* data() const { return &_data[0]; };

    // capacity
    constexpr std::size_t capacity() const { return SizeN; }

    // size
    constexpr std::size_t size() const { return _size; };

    // clear
    constexpr void clear() { _size = 0; }

    // empty
    constexpr bool empty() const { return 0 == _size; }

    // underlying array const
    constexpr const std::array<CharT, SizeN>&  underlying_array() const { return _data; }

    // underlying array non const
    constexpr std::array<CharT, SizeN>&  underlying_array() { return _data; }

private:

    std::array<CharT, SizeN>        _data;
    std::size_t                     _size;
};

// << operator for basic_sstring
template <typename CharT, std::size_t SizeN>
std::basic_ostream<CharT>& operator << (std::basic_ostream<CharT>& out,
                                        const basic_sstring<CharT, SizeN>& sstr_)
{
    std::copy(sstr_._data.begin(), sstr_._data.begin() + sstr_._size, std::ostreambuf_iterator<char>(out));
    return out;
}

// User-defined deduction guides
template <typename CharT>
basic_sstring(const std::basic_string<CharT>&) -> basic_sstring<CharT, 512>;
template <typename CharT>
basic_sstring(const CharT*) -> basic_sstring<CharT, 512>;


typedef basic_sstring<char> sstring;

#endif

使用以下代码对其进行了测试:

#include <iostream>
#include <string>

#include "SString.h"

int main()
{
    // Testing constructors and addition operators
    sstring a = " First ";
    sstring b = a + std::string(" Second ");
    sstring c = a + b + " Third ";
    c += c;

    std::cout << "A = " << a << std::endl; //  First
    std::cout << "B = " << b << std::endl; //  First  Second 
    std::cout << "C = " << c << std::endl; //  First  First  Second  Third  First  First  Second  Third 

    // Testing comparison operators
    sstring d = " Fourth ";
    std::cout << "Comparison 1: " << (d == " Fourth ") << std::endl; // 1
    std::cout << "Comparison 2: " << (d == std::string(" Fourth ")) << std::endl; // 1
    std::cout << "Comparison 3: " << (d == sstring(" Fourth ")) << std::endl; // 1
    std::cout << "Comparison 4: " << (d == sstring(" Fifth ")) << std::endl; // 0
    std::cout << "Comparison 5: " << (d != " Fifth ") << std::endl; // 1
    std::cout << "Comparison 6: " << (d != std::string(" Fifth ")) << std::endl; // 1
    std::cout << "Comparison 7: " << (d != sstring(" Fifth ")) << std::endl; // 1
    std::cout << "Comparison 8: " << (d != sstring(" Fourth ")) << std::endl; // 0
    return 0;
}

【问题讨论】:

    标签: c++ c++17


    【解决方案1】:

    模板推导发生在重载决议和任何可能的参数转换之前。所以你的运营商:

    basic_sstring<CharT, SizeT> operator + (const basic_sstring<CharT, SizeT>& first_,
                                            const basic_sstring<CharT, SizeT>& second_)
    

    不考虑,除非编译器可以为 both 参数推导出 CharTSizeT。当然,当一个参数是文字时,它就不能。

    “真正的”std::stringa dozen overloads of operator+ 来克服这个问题。

    【讨论】:

    • 所以这意味着我需要为我需要的任何转换创建重载,比如 const char* 和 std::string?还有其他方法可以解决吗?另外我想这需要为所有需要的操作员完成?
    • 原则上是的。问题是编译器首先收集可能的重载,并且只有 then 考虑参数的任何转换。如果模板参数不能推导出来,模板在第一轮就已经被丢弃了。
    【解决方案2】:

    这是一件非常令人沮丧的事情,我也曾与之抗争过。您必须尝试使用​​基于 SFINAE 的方法:

    template <typename T1, typename T2,
        typename String1 = decltype(basic_sstring(std::declval<T1>())),
        typename String2 = decltype(basic_sstring(std::declval<T2>()))>
    constexpr bool operator==(const T1 &string1, const T2 &string2);
    

    它使用class template argument deduction,因此您可能需要为基于 basic_string 的构造函数添加用户定义的推导指南:

    template <typename CharT>
    basic_sstring(const std::basic_string<CharT>&) -> basic_sstring<CharT, 512>;
    

    【讨论】:

    • 我觉得这很有趣(我对这一切都很陌生)。你有时间在(比如)Wandbox 上做一个最小的现场演示吗?
    • @PaulSanders,对不起,我没有。但是,如果您打算自己尝试这种方法并遇到一些麻烦,请不要犹豫,要求澄清。
    • 好的,可能会尝试,具体取决于。谢谢。
    • @r3mus n0x 谢谢..它现在工作正常。唯一的问题是字符串在每个运算符的定义中创建了参数的额外副本。但是因为没有堆分配是好的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-29
    相关资源
    最近更新 更多