浅墨浓香

想要天亮进城,就得天黑赶路。

导航

7.4 Dealing with String Literals and Raw Arrays

7.4 处理字符串常量和原生数组

 

So far, we have seen the different effects for templates parameters when using string literals and raw arrays:

到目前为止,我们己经看到了按值传递和按引用传递字符串常量和原生数组的效果是不同的:

  • Call-by-value decays so that they become pointers to the element type.

   按值传递时参数类型会发生退化,成为指向元素类型的指针。

  • Any form of call-by-reference does not decay so that the arguments become references that still refer to arrays.

   任何形式的按引用传递都不会退化,参数类型仍然是一个指向数组的引用。

Both can be good and bad. When decaying arrays to pointers, you lose the ability to distinguish between handling pointers to elements from handling passed arrays. On the other hand, when dealing with parameters where string literals may be passed, not decaying can become a problem, because string literals of different size have different types. For example:

两种方式各有优缺点。当按值传递,数组退化为指针类型,就无法区分是在处理指针还是数组。另一方面,如果按引用传递字符串常量,那么类型不发生退化也会带来一个问题,因为不同长度的字符串常量具有不同的类型。例如:

template<typename T>
void foo (T const& arg1, T const& arg2)
{
    …
}

foo("hi", "guy"); //ERROR

Here, foo("hi","guy") fails to compile, because "hi" has type char const[3], while "guy" has type char const[4], but the template requires them to have the same type T. Only if the string literals were to have the same length would such code compile. For this reason, it is strongly recommended to use string literals of different lengths in test cases.

在这里,foo("hi","guy")会编译失败。因为"hi"的类型为char const[3],而"guy"的类型为char const[4]。但是函数模板要求两者的类型必须相同。只有在字符串常量的长度相同时,这段代码才能编译通过。因此,强烈建议在测试代码中使用不同长度的字符串常量。

By declaring the function template foo() to pass the argument by value the call is possible:

如果将函数模板foo()声明为按值传递时,那么这个调用就可能通过:

template<typename T>
void foo (T arg1, T arg2)
{
    …
}

foo("hi", "guy"); //compiles, but …

But, that doesn’t mean that all problems are gone. Even worse, compile-time problems may have become run-time problems. Consider the following code, where we compare the passed argument using operator==:

但是这并不意味着问题己经解决。事实上,情况可能变得更遭:它将一个编译期的问题变成了运行期的问题。考虑下列代码,我们通过==运算符来比较两个参数:

template<typename T>
void foo (T arg1, T arg2)
{
    if (arg1 == arg2) { //OOPS: 比较传递进来的数组地址
        …
    }
}

foo("hi", "guy"); //compiles, but …

As written, you have to know that you should interpret the passed character pointers as strings. But that’s probably the case anyway, because the template also has to deal with arguments coming from string literals that have been decayed already (e.g., bycoming from another function called by value or being assigned to an object declared with auto).

如上所写,你应该很清楚,这里需要将字符指针解释为字符串(string)。但不管怎么,情况可能就是这样【译注:它们实际上为数组类型,却要退化为指针来处理】,因为模板还要处理类型可能己经退化的字符串常量(例如:它们可能来自另一个按值传递的函数,或者对象是通过auto声明的)。【译注:当按值传递时,数组类型会退化成指针。而本例通过直接比较两个(数组的)地址来判断字符串是否相同,显然是不对的。这也是按值传递所带来的问题,实际上这时我们需要知道传入的是指针还是数组,而不是一味地当成指针来处理】。

 

Nevertheless, in many cases decaying is helpful, especially for checking whether two objects (both passed as arguments or one passed as argument and the other expecting the argument) have or convert to the same type. One typical usage is perfect forwarding. But if you want to use perfect forwarding, you have to declare the parameters as forwarding references. In those cases, you might explicitly decay the arguments using the type trait std::decay<>(). See the story of std::make_pair() in Section 7.6 on page 120 for a concrete example.

不过,在许多情况下类型退化是有用的,特别是检查两个对象(两个对象都是参数,或者一对象是参数,并用它给另一个赋值)是否具有相同类型或可转换为同一种类型。一种经典的应用就是完美转发。但是使用完美转发时必须将参数声明为转发引用,在这种情况下,你应该使用 std::decay<>()显式地将参数声明为退化类型(decay)。有关具体事例,请参阅第120页7.6节中的std::make_pair()。

 

Note that other type traits sometimes also implicitly decay, such as std::common_type<>, which yields the common type of two passed argument types (see Section 1.3.3 on page 12 and Section D.5 on page 732).

还需要注意,其他的类型萃取有时也会隐式类型退化。如std::common_type<>,它会返回两个参数的公共类型(见第12页1.3.3节以及第732页的D.5节)

 

7.4.1 Special Implementations for String Literals and Raw Arrays

7.4.1 字符串常量和原生数组的特殊实现

 

You might have to distinguish your implementation according to whether a pointer or an array was passed. This, of course, requires that a passed array wasn’t decayed yet.

有时可能需要对数组参数和指针参数做不同的实现。当然,这要求传入的数组未发生退化。

 

  To distinguish these cases, you have to detect whether arrays are passed. Basically, there are two options:

  要区分这些情况,必须检查要传入的是不是一个数组。基本上,有两种选择:

  • You can declare template parameters so that they are only valid for arrays:

  可以将模板定义成只能接受数组作为参数:

template<typename T, std::size_t L1, std::size_t L2>
void foo(T (&arg1)[L1], T (&arg2)[L2])
{
    T* pa = arg1; // decay arg1
    T* pb = arg2; // decay arg2

    if (compareArrays(pa, L1, pb, L2)) {
        …
    }
}

Here, arg1 and arg2 have to be raw arrays of the same element type T but with different sizes L1 and L2. However, note that you might need multiple implementations to support the various forms of raw arrays (see Section 5.4 on page 71).

这里的arg1和arg2必须是具有相同元素类型,长度可能不同的两个原生数组。但是,为了支持多种格式的原生数组,可能需要多种不同的实现(见第71页的5.4节)。

• You can use type traits to detect whether an array (or a pointer) was passed:

 也可以使用类型萃取,判断传入的是不是数组类型(或指针类型):

template<typename T, typename = std::enable_if_t<std::is_array_v<T>>>
void foo (T&& arg1, T&& arg2)
{
    …
}

Due to these special handling, often the best way to deal with arrays in different ways is simply to use different function names. Even better, of course, is to ensure that the caller of a template uses std::vector or std::array. But as long as string literals are raw arrays, we always have to take them into account.

由于这些特殊的处理过于复杂。通常,最佳方法就是简单地使用不同的函数名称来处理数组参数。当然,如果能要求模板的调用者使用std::vector或std::array来传递参数那就更好了。但是只要字符串常量还是原生数组,我们就必须加以重视。