【问题标题】:How to deduce array size out of std::array<T, N>::pointer member/dependent type?如何从 std::array<T, N>::pointer 成员/依赖类型中推断出数组大小?
【发布时间】:2016-04-16 00:31:11
【问题描述】:

我的目标是为strcpy 编写安全替换,以防在编译期间知道目标缓冲区大小的情况,并且我希望推断缓冲区大小,因此用户不需要知道它。例如:

char xs[2] = { 0 };
strcpy(xs, "abc"); // buffer overflow!
printf("[%s]\n", xs);

输出将(希望)是:

[abc]

对于简单的情况,当传递C风格的数组时,可以毫不费力地写出来:

template<size_t N>
char * safe_strcpy(char (& dst)[N], const char * src) noexcept {
    std::snprintf(dst, N, "%s", src);
    return & dst[0];
}

推导出数组的大小,snprintf 负责放置终止空字节,瞧。

我也可以让它适应 std::array:

template<size_t N>
typename std::array<char, N>::pointer
safe_strcpy(std::array<char, N> & dst, const char * src) noexcept {
    std::snprintf(dst.data(), N, "%s", src);
    return dst.data();
}

但这个版本并不是真正的替代品:

std::array<char, 2> ys = {};
strcpy(ys.data(), "abc"); // overflow!
safe_strcpy(ys, "abc");   // ok, but I needed to remove .data()

我希望以下情况正常工作:

safe_strcpy(ys.data(), "abc"); // "a" should appear in buffer

ys.data() 的依赖类型是 std::array&lt;char, 2u&gt;::pointer {aka char*},所以我认为应该可以从中推断出数组大小,但我不知道如何:/

当我尝试这样的事情时:

template<size_t N>
typename std::array<char, N>::pointer
safe_strcpy(typename std::array<char, N>::pointer & dst, const char * src) {
    // etc...
}

编译失败并出现错误:

error: no matching function for call to ‘safe_strcpy(std::array<char, 2u>::pointer, const char [4])’
safe_strcpy(ys.data(), "abc");
                            ^
(...)
note:   template argument deduction/substitution failed:
note:   couldn't deduce template parameter ‘N’

我尝试使用 gcc 5.1.1 和 clang 3.5.0,两者的错误基本相同。是否可以从 C++ 中的依赖类型中推断出类型?

[编辑] 对所有善良的人说,我应该使用 std::string - 你错过了这里的重点。我可以用任何 STL 容器和::iterator 而不是::pointer 写同样的问题。

【问题讨论】:

  • 不可能,抱歉。
  • 认真的吗?但是编译器知道这种类型(它甚至在错误消息中打印它......)。
  • 编译器知道您编写调用时的类型,但它不知道函数内的类型。额外的信息只是用于调试 - .data() is char*。编译器错误消息是“这个 char* 来自这个类型表达式”,但这不是类型的名称。
  • char*,你不可能推断出是std::array&lt;char,2&gt;::pointer还是std::array&lt;char,3&gt;::pointer。但是,您可以想象创建一个sized_pointercoliru.stacked-crooked.com/a/44c24a8463b6673b
  • @PatrykObara,你为什么不做一个没有.data() 部分的std::array 的重载?然后你就可以推断出大小了。

标签: c++ arrays c++11 c++14


【解决方案1】:

命名空间stdtemplate&lt;class T&gt; arraydata() 成员 - 根据 C++11 标准 - 声明如下

T * data() noexcept;
const T * data() const noexcept;

但不是这样:

pointer data() noexcept;
const_pointer data() const noexcept;

即使它是使用 typedef 声明的,也没有区别。考虑您的示例代码:

std::array<char, 2> ys = {}; // 1
strcpy(ys.data(), "abc"); // 2
safe_strcpy(ys, "abc"); // 3

// 1

编译器实例化std::array&lt;char, 2&gt;。这使得 typedef pointer = char* 并编译(如果曾经使用过)具有以下签名的虚构成员 pointer data()

char* data();

typedef 被替换是因为 typedef 和别名是程序员的语法糖,而不是编译器。编译器知道这是char* 所以它就在那里。

// 2

您使用(作为您的第一个参数)具有签名char*(void) 的函数调用模板。 (同样std::array&lt;char,2&gt;::pointer 不是它自己的类型,而是char*)。因此,调用是void(char*, char const*),这就是编译器试图从中推断出模板的内容。而且这个调用没有显示任何关于数组大小的信息,甚至不知道指针最初来自数组的事实。

// 3

你的电话来了

void(std::array<char, 2> &, char const *);

如果需要,编译器可以推断大小甚至字符类型。

【讨论】:

  • 好的,在这种情况下你是对的,但(2)只是部分答案;即使std::array&lt;T,N&gt;::pointer 是真实类型(std::array&lt;T,N&gt; 中的子类,而不仅仅是 typedef) - 匹配仍然会失败(错误完全相同)。那是因为 C++ 标准 (14.8.2.4/4) 说,::&lt;type&gt; 之前的所有内容都是“非推导的”,编译器甚至不会尝试匹配它。我再次对 C++ 感到失望 :(
  • @PatrykObara:对于std::array,它不仅是部分的,而且你是对的,即使在std::array&lt;T, N&gt;::type 之后的类类型的情况下N 也是不可演绎的。
【解决方案2】:

您遇到的问题是std::array&lt;char, N&gt;::pointerchar* 对于所有N,所以当您调用ys.data() 时,您将char* 传递给函数并且有无限N 都匹配。这编译器会因为模棱两可而失败。我查看了array,但找不到任何方法来创建您正在寻找的替代品。

但是由于您正在编写 C++,您可以通过使用 std::string 而不是尝试处理 C 字符串来解决问题。

【讨论】:

    【解决方案3】:

    正如其他人所说,std::array 中没有指针成员具有有关大小的信息,特别是 pointer 没有该信息。

    一个非常不可移植的替代方法是在 GNU C++ 中使用 ._M_elems 而不是 .pointer

    int main(){
      std::array<double, 10> arr;
      assert( std::extent<decltype(arr._M_elems)>() == arr.size() );
    }
    

    std::array 应该有标准的carraycarray_type 成员吗?也许是的。虽然这个变量在指针中很容易衰减,所以它不会很有用。

    【讨论】:

      猜你喜欢
      • 2020-05-21
      • 2017-02-17
      • 2012-12-05
      • 1970-01-01
      • 2014-12-27
      • 2016-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多