2

const char[N] と std::string の両方をパラメーターとして取り、パラメーターの型に応じて異なる動作を実行する可変個引数テンプレートを作成するにはどうすればよいですか?

これまでのところ、可変個引数テンプレートは次のようになっています。

template<typename T>
void Capitalize_And_Output(T&& str) {
    std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile with char*s
    std::cout << str << std::endl;  
    return;
}

template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
    std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile with char*s
    std::cout << str << " ";
    Capitalize_And_Output(std::forward<Strings>(rest)...);
    return;
}

「ユニバーサル」参照を使用すると、すべてが関数に受け入れられます。
ただし、次のように関数を呼び出しても機能しません。

std::string hello = "hello";
std::string earth = "earth";

//fails because "planet" is a const char[N] and not a std::string
Capitalize_And_Output(hello,"planet","earth"); //outputs: "HELLO PLANET EARTH"

次のようにするとうまくいきます:

Capitalize_And_Output(hello,std::string("planet"),"earth"); //outputs: "HELLO PLANET EARTH"

しかし、ユーザーがこの変換を行う責任を負いたくありません。代わりに、その責任をテンプレート関数に渡すにはどうすればよいですか?

型特性を使用して決定を下そうとしていますが、成功していません。私は使用しようとしていました:

std::is_same<First, std::string&>::value   

しかし、分岐の決定方法がわかりませんでした。これがifステートメント内で機能するとは思わない。

たぶん std::conditional を使用する必要がありますか?テンプレートに auto&& 型のローカル変数を作成して解決する必要があるのではないでしょうか? 私が試したさまざまなことで、これまでのところ成功していません。

4

4 に答える 4

4

I see two issues with Simple's solution:

(1) It fails to compile this test case

std::string hello = "hello";
const std::string earth = "earth";
Capitalize_And_Output(hello, "planet", earth);

because earth is a const std::string and there's no overload which can take this call. (Try it!)

(2) It fails to compile for types (other than const char* and alikes) that are convertible to std::string, for instance,

struct beautiful {
    operator std::string() const {
        return "beautiful";
    }
};

Capitalize_And_Output(hello, beautiful{}, "planet", earth);

The following implementations solves these issues:

New solution: My old solution (below) works but it's not efficient for char*, char[N]. In addition, it's complicate and uses some overload resolution trickery to avoid ambiguities. This one is simpler and more efficient.

void Capitalize_And_Output_impl(const char* str) {
    while (char c = toupper(*str++))
        std::cout << c;
}

void Capitalize_And_Output_impl(std::string& str) {
    std::transform(str.begin(), str.end(), str.begin(), toupper);
    std::cout << str;
}

void Capitalize_And_Output_impl(const std::string& str) {
    Capitalize_And_Output_impl(str.data());
}

template<typename First>
void Capitalize_And_Output(First&& str) {
    Capitalize_And_Output_impl(std::forward<First>(str));
    std::cout << '\n';
}

template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
    Capitalize_And_Output_impl(std::forward<First>(str));
    std::cout << ' ';
    Capitalize_And_Output(std::forward<Strings>(rest)...);
}

Because I don't use std::transform (except for the second overload), it doesn't need to know the size of the string in advance. Therefore, for a char* there's no need to call std::strlen (as in other solutions).

A small detail to notice is that this implementation only prints space between words. (It doesn't print one after the last word.)

Old solution:

void Capitalize_And_Output_impl(std::string& str, int) {
    std::transform(str.begin(), str.end(), str.begin(), ::toupper);
    std::cout << str << ' ';
}

void Capitalize_And_Output_impl(std::string str, long) {
    Capitalize_And_Output_impl(str, 0);
}

void Capitalize_And_Output() {
    std::cout << '\n';
}

template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
    Capitalize_And_Output_impl(std::forward<First>(str), 0);
    Capitalize_And_Output(std::forward<Strings>(rest)...);
}

I guess the two Capitalize_And_Output_impl overloads deserve explanations.

Firstly disconsider the second argument (int/long). The first overload can take non const lvalues which are capitalized on exit (as requested by Trevor Hickney in a comment to Simple's solution ).

The second oveload is meant to take everything else i.e., rvalues and const lvalues. The idea is to copy the argument to an lvalue which is then passed to the first overload. This function could naturally be implemented in this way (still disconsidering the second argument):

void Capitalize_And_Output_impl(const std::string& str) {
    std::string tmp(str);
    Capitalize_And_Output_impl(tmp);
}

This work as required. However, a famous article by Dave Abrahams explains that when you take an argument by reference to const and copies it inside your function (as above), it's better to take the argument by value (because in some circunstances the compiler might avoid the copy). In summary, this implementation is preferable:

void Capitalize_And_Output_impl(std::string str) {
    Capitalize_And_Output_impl(str);
}

Unfortunately, as for the first overload, calls to Capitalize_And_Output_impl on lvalues can also be directed to this overload. This yields an ambiguity which the compiler complains about. That's why we need the second argument.

The first overload takes an int and the second takes a long. Therefore, passing the literal 0, which is an int, makes the first overload preferable over the second but only when the ambiguity arises. In the other cases, i.e., when the first argument is an rvalue or const lvalue the first overload cannot be used whereas the second one can after the literal 0 is promoted to long.

Two final remarks. (1) if you want to avoid the recursive call in Capitalize_And_Output (I guess this is just a question of taste), then you can use the same trickery as in Simple's solution (through unpack) and (2) I don't see the need to pass the lambda wrapping ::toupper as in Simple's solution.

于 2013-10-28T17:53:03.917 に答える
2

これには型特性は必要ありません。

char safer_toupper(unsigned char const c)
{
    return static_cast<char>(std::toupper(c));
}

void Capitalize_And_Output_Impl(std::string& str)
{
    auto const first = str.begin();
    std::transform(first, str.end(), first, safer_toupper);
    std::cout << str;
}

void Capitalize_And_Output_Impl(std::string const& str)
{
    std::transform(str.begin(), str.end(),
                   std::ostreambuf_iterator<char>(std::cout),
                   safer_toupper);
}

void Capitalize_And_Output_Impl(char const* const str)
{
    std::transform(str, str + std::strlen(str),
                   std::ostreambuf_iterator<char>(std::cout),
                   safer_toupper);
}

template<typename... Strings>
void Capitalize_And_Output(Strings&&... rest)
{
    int const unpack[]{0, (Capitalize_And_Output_Impl(rest),
                           std::cout << ' ', 0)...};
    static_cast<void>(unpack);
    std::cout << std::endl;
}
于 2013-10-28T16:13:05.843 に答える
1

このバージョンでは、引数の不必要なコピーが行われず、不必要な一時文字列が導入されずstrlen()、コンパイル時に長さがわかっているリテラル文字列の呼び出しが回避されます。

#include <algorithm>
#include <cctype>
#include <cstring>
#include <iostream>
#include <iterator>
#include <string>
#include <type_traits>
#include <vector>

template<typename I> void CapitalizeAndOutputImpl(I first, I last) {
    std::string t;
    std::transform(first, last, std::back_inserter(t), std::toupper);
    std::cout << t << " ";
}

template<typename T>
struct CapitalizeAndOutputHelper {
    void operator()(const T& s) {
        CapitalizeAndOutputImpl(std::begin(s), std::end(s));
    }
};

template<typename T>
struct CapitalizeAndOutputHelper<T*> {
    void operator()(const T* s) {
        CapitalizeAndOutputImpl(s, s + std::strlen(s));
    }
};

template<typename T> void CapitalizeAndOutput(T&& s) {
    CapitalizeAndOutputHelper<std::remove_reference<T>::type>()(s);
    std::cout << std::endl;
}

template<typename First, typename... Rest> void CapitalizeAndOutput(First&& first, Rest&&... rest) {
    CapitalizeAndOutputHelper<std::remove_reference<First>::type>()(first);
    CapitalizeAndOutput(rest...);
}

int main() {
    std::string hello{ "string hello" };
    const std::string world{ "const string world" };
    char arrHello[] = "char[] hello";
    const char vHelloInit[] = "char* hello";
    std::vector<char> vHello(std::begin(vHelloInit), std::end(vHelloInit));
    const char* cworld = "const char* world";
    CapitalizeAndOutput(hello, world, arrHello, "literal world", vHello.data(), cworld);
}
于 2013-10-28T18:22:18.200 に答える
0

最も簡単なのは、2 つのオーバーロードを使用することです。

void do_stuff() {}
template<class...Ts>
void do_stuff(std::string s, Ts&&... ts);

2番目に既存の体を使用します。

完全な転送が行われ、変換と出力の直前にコピーされます。

変異を伝播させたい場合は、おそらく間違っています。あなたが主張するなら、@ Cassioのアプローチはまともに見えます.

于 2013-10-29T11:29:50.297 に答える