12

This is the C++ program:

#include <iostream>
#include <vector>
#include <numeric>
using namespace std;

int test_string(const string & str) {
    return str.size();
}

void main() {
    test_string("");                                     //can compile
    vector<string> v;
    string sum = accumulate(v.cbegin(), v.cend(), "");   //cannot compile
}

I want to use implicit conversion from const char * to string in the call of generic STL function accumulate. I know that conversion from const char * to string is not explicit, so we can pass const char * parameter to calls in which a string type is required. This can be proved by the above test_string function. But when I did the same thing in accumulate, the compiler complain:

error C2440: '=': cannot convert from 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>' to 'const char *'

The code works only when I replaced "" with string(""). I don't understand why the implicit conversion works for my custom function but does not work in accumulate. Can you explain that? Thanks a lot.

PS: I am using Visual Studio 2015.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
user5280911
  • 723
  • 1
  • 8
  • 21
  • 2
    `void main()` -- No, it should be `int main()` – PaulMcKenzie Jun 16 '17 at 05:52
  • 2
    "I want to use implicit conversion from const `char *` to `string`"... But the compiler does not know that you "want" that. There's absolutely nothing in expression `accumulate(v.cbegin(), v.cend(), "")` to suggest that `""` should be implicitly converted to `std::string`. Why do you expect the compiler to perform the conversion? How do you expect the compiler to figure out that the conversion is necessary? – AnT stands with Russia Jun 16 '17 at 06:02

3 Answers3

12

Take a look at the possible implementation from cppreference

template<class InputIt, class T>
T accumulate(InputIt first, InputIt last, T init)
{
    for (; first != last; ++first) {
        init = init + *first;
    }
    return init;
}

When you call the function the way you did, InputIt will be deduced as a vector<string>::const_iterator and T will be deduced to be a const char*. As you can see here in the for loop, the line of code that does the "accumulation" is this

init = init + *first

Here on the right hand side of the assignment *first will evaluate to a string& and init will evaluate to a const char*. Then you will be using the std::string::operator+ which will concatenate the const char* and the std::string instance to get a std::string back. And then you are trying to assign a std::string to a const char* variable. This is not legal.

This will not work as std::string objects are not implicitly convertible or assignable to const char*, the reverse is true however.

To fix this change your code to the following (note that I postfixed the string literal with a s, which is C++14 syntax for a user defined literal (which in this case evaluates to a std::string) http://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s

int main() {
    using namespace std::string_literals;
    vector<string> v;
    string sum = accumulate(v.cbegin(), v.cend(), ""s); 
}

Also as noted in the comments, change void main() to int main(). For more see What should main() return in C and C++?

Curious
  • 20,870
  • 8
  • 61
  • 146
  • This is a very useful answer. My problem is not that I don't know char can't + string, but that I don't understand why the implicit conversion is not triggered. So I did not tick your answer. But I thank you very much for your help and the rich knowledge contained. – user5280911 Jun 16 '17 at 06:20
  • @user5280911 The possible explanation was an attempt to explain why the implicit conversion was not being triggered. Is there something you did not follow in the answer? – Curious Jun 16 '17 at 06:30
12

std::accumulate is declared as

template< class InputIt, class T >
T accumulate( InputIt first, InputIt last, T init );

That means the template argument T is deduced from the argument passed in (i.e. ""). Then it'll be const char*. On the other hand, how could the compiler perform the implicit conversion? Which type should be the target type?

You can pass a std::string explicitly, or specify the template argument explicitly. e.g.

// pass a std::string exactly
string sum = accumulate(v.cbegin(), v.cend(), string(""));

// T is specified as std::string explicitly
// "" will be implicitly converted to std::string
string sum = accumulate<decltype(v.cbegin()), string>(v.cbegin(), v.cend(), "");
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
2

I don't understand why the implicit conversion works for my custom function but does not work in accumulate. Can you explain that?

Implicit conversion isn't even attempted, std::accumulate simply tries to accumulate by adding instances of std::string to a sum that's initialized as auto sum = ""; and you get the same error that you would get in this case:

std::string s = "abc";
const char* sum = "";
sum = sum + abc; // <-- error

The code works only when I replaced "" with string("")

Because this way internally accumulator's type is std::string and everything works as intended. You may as well do this:

string sum = accumulate(v.cbegin(), v.cend(), ""s);

As a side note, it should be int main() { ... }, not void main

Pavel P
  • 15,789
  • 11
  • 79
  • 128