类模板中不发生隐式转换

3

我正在编写一个简单的基于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描述的方法进行操作(还有一些其他变化,比如必须在类本身中定义友元函数)。我的代码还有一些错误,但是在清除它们后,它可以正常工作。以下是可供参考的有效sstring代码和测试代码。

#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;
}
2个回答

3

模板推导发生在重载解析和参数的任何可能转换之前。因此,你的操作符:

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

除非编译器能够推断出CharTSizeT的参数类型,否则这个问题就不会被考虑。当一个参数是字面量时,编译器当然无法推断。

"真正的"std::string十几种重载的operator+来解决这个问题。


那就意味着我需要为我需要的每种转换创建重载,比如const char*和std::string?还有其他可用的方法来解决它吗?此外,我认为所有需要的运算符都需要这样做? - john_zac
原则上是可以的。问题在于编译器首先收集可能的重载,然后才考虑参数的任何转换。如果无法推导出模板参数,则模板将在第一轮中被丢弃。 - Bo Persson

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);

它使用类模板参数推导,因此您可能需要为基于basic_string的构造函数添加用户定义的推导指南:
template <typename CharT>
basic_sstring(const std::basic_string<CharT>&) -> basic_sstring<CharT, 512>;

我觉得这非常有趣(我对这一切还很陌生)。你有时间在(比如)Wandbox上放一个最小的实时演示吗? - Paul Sanders
@PaulSanders,抱歉,我不知道。但是如果你自己尝试这种方法并遇到了一些麻烦,请不要犹豫,随时向我询问澄清。 - r3mus n0x
好的,可能会尝试,取决于情况。谢谢。 - Paul Sanders
@r3mus n0x 谢谢,现在它可以正常工作了。唯一的问题是 sstring 在每个运算符的定义中会创建参数的额外副本。但由于没有堆分配,所以这没关系。 - john_zac

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