Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does implicit conversion not work in accumulate?

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.

like image 442
user5280911 Avatar asked Jun 16 '17 05:06

user5280911


3 Answers

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(), "");
like image 106
songyuanyao Avatar answered Nov 13 '22 13:11

songyuanyao


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++?

like image 12
Curious Avatar answered Nov 13 '22 13:11

Curious


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

like image 2
Pavel P Avatar answered Nov 13 '22 13:11

Pavel P