Can I ask why the code for vector push_back depending on the data type doesn't work?

Viewed 142

I want to fill a vector with different functions depending on the data type of vector. Now I have three types to consider: int, double, string. If I only consider int and double, the code works perfectly. But if I include string into the template, the code fails. I don't understand why the if statement for string doesn't work. Here is the code:

#include <iostream>
#include <vector>
#include <map>
#include <string>

template <typename T>
void fillV(std::vector<T> &res) {
    if (std::is_same<T, double>::value ) res.push_back(3.5);
    else if (std::is_same<T, int>::value) res.push_back(2);
    else if (std::is_same<T, std::string>::value) res.push_back(std::string("hello"));
}

int main()
{
    std::vector<std::string> t;
    std::vector<double> t1;
    std::vector<int> t2;
     
    fillV<std::string>(t);
    fillV<double>(t1);
    fillV<int>(t2);
    
    std::cout << t[0] << "\n";
    std::cout << t1[0] << "\n";
    std::cout << t2[0] << "\n";
}

The error information is

  
input
stderr
Compilation failed due to following error(s).main.cpp: In instantiation of ‘void fillV(std::vector<T>&) [with T = std::basic_string<char>]’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',27)">main.cpp:27:25</span>:   required from here
main.cpp:16:42: error: no matching function for call to ‘std::vector >::push_back(double)’
     if (std::is_same<T, double>::value ) res.push_back(3.5);
                                          ^~~
In file included from /usr/include/c++/6/vector:64:0,
                 from main.cpp:10:
/usr/include/c++/6/bits/stl_vector.h:914:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::basic_string; _Alloc = std::allocator >; std::vector<_Tp, _Alloc>::value_type = std::basic_string]
       push_back(const value_type& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:914:7: note:   no known conversion for argument 1 from ‘double’ to ‘const value_type& {aka const std::basic_string&}’
/usr/include/c++/6/bits/stl_vector.h:932:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = std::basic_string; _Alloc = std::allocator >; std::vector<_Tp, _Alloc>::value_type = std::basic_string]
       push_back(value_type&& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:932:7: note:   no known conversion for argument 1 from ‘double’ to ‘std::vector >::value_type&& {aka std::basic_string&&}’
main.cpp:17:43: error: no matching function for call to ‘std::vector >::push_back(int)’
     else if (std::is_same<T, int>::value) res.push_back(2);
                                           ^~~
In file included from /usr/include/c++/6/vector:64:0,
                 from main.cpp:10:
/usr/include/c++/6/bits/stl_vector.h:914:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::basic_string; _Alloc = std::allocator >; std::vector<_Tp, _Alloc>::value_type = std::basic_string]
       push_back(const value_type& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:914:7: note:   no known conversion for argument 1 from ‘int’ to ‘const value_type& {aka const std::basic_string&}’
/usr/include/c++/6/bits/stl_vector.h:932:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = std::basic_string; _Alloc = std::allocator >; std::vector<_Tp, _Alloc>::value_type = std::basic_string]
       push_back(value_type&& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:932:7: note:   no known conversion for argument 1 from ‘int’ to ‘std::vector >::value_type&& {aka std::basic_string&&}’
main.cpp: In instantiation of ‘void fillV(std::vector<T>&) [with T = double]’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',28)">main.cpp:28:21</span>:   required from here
main.cpp:18:51: error: no matching function for call to ‘std::vector::push_back(std::string)’
     else if (std::is_same<T, std::string>::value) res.push_back(std::string("hello"));
                                                   ^~~
In file included from /usr/include/c++/6/vector:64:0,
                 from main.cpp:10:
/usr/include/c++/6/bits/stl_vector.h:914:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = double; _Alloc = std::allocator; std::vector<_Tp, _Alloc>::value_type = double]
       push_back(const value_type& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:914:7: note:   no known conversion for argument 1 from ‘std::string {aka std::basic_string}’ to ‘const value_type& {aka const double&}’
/usr/include/c++/6/bits/stl_vector.h:932:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = double; _Alloc = std::allocator; std::vector<_Tp, _Alloc>::value_type = double]
       push_back(value_type&& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:932:7: note:   no known conversion for argument 1 from ‘std::string {aka std::basic_string}’ to ‘std::vector::value_type&& {aka double&&}’
main.cpp: In instantiation of ‘void fillV(std::vector<T>&) [with T = int]’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',29)">main.cpp:29:18</span>:   required from here
main.cpp:18:51: error: no matching function for call to ‘std::vector::push_back(std::string)’
     else if (std::is_same<T, std::string>::value) res.push_back(std::string("hello"));
                                                   ^~~
In file included from /usr/include/c++/6/vector:64:0,
                 from main.cpp:10:
/usr/include/c++/6/bits/stl_vector.h:914:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = int; _Alloc = std::allocator; std::vector<_Tp, _Alloc>::value_type = int]
       push_back(const value_type& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:914:7: note:   no known conversion for argument 1 from ‘std::string {aka std::basic_string}’ to ‘const value_type& {aka const int&}’
/usr/include/c++/6/bits/stl_vector.h:932:7: note: candidate: void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = int; _Alloc = std::allocator; std::vector<_Tp, _Alloc>::value_type = int]
       push_back(value_type&& __x)
       ^~~~~~~~~
/usr/include/c++/6/bits/stl_vector.h:932:7: note:   no known conversion for argument 1 from ‘std::string {aka std::basic_string}’ to ‘std::vector::value_type&& {aka int&&}’

Updated on 04/14/2021: Since I can't answer my own question, I will add one more solution other than the overloading, which is by Partial template specialization. It works before C++17. The code is inspired by the first answer, thank you.

template <typename T, int i>
struct test;

template <typename T>
struct test<T, 1> {
    void fillV(std::vector<T> &res) {
    res.push_back(3.5);
}
};

template <typename T>
struct test<T, 2> {
    void fillV(std::vector<T> &res) {
    res.push_back(2);
}
};

template <typename T>
struct test<T, 3> {
    void fillV(std::vector<T> &res) {
    res.push_back("Hello");
}
};

int main()
{
    std::vector<std::string> t;
    std::vector<double> t1;
    std::vector<int32_t> t2;
     
    test<double, 1> a1;
    a1.fillV(t1);
    test<int32_t, 2> a2;
    a2.fillV(t2);
    test<std::string, 3> a;
    a.fillV(t);
    
    std::cout << t[0] << "\n";
    std::cout << t1[0] << "\n";
    std::cout << t2[0] << "\n";
}

Updated on 04/17/2021: Moreover, if overloading is used, the following code would be enough.

void fill_v(std::vector<double>& res) { res.push_back(3.5); }
void fill_v(std::vector<int>& res) { res.push_back(2); }
void fill_v(std::vector<std::string>& res) { res.push_back(std::string("hello")); }

int main()
{
    std::vector<std::string> t;
    std::vector<double> t1;
    std::vector<int32_t> t2;

    fillV(t1);
    fillV(t2);
    fillV(t);

    std::cout << t[0] << "\n";
    std::cout << t1[0] << "\n";
    std::cout << t2[0] << "\n";
}

1 Answers

if performs dispatch at run-time. At compile time, all the if part and else part need to be valid whatever T is.

You can change to Constexpr If (since C++17).

If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

If a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated.

E.g.

template <typename T>
void fillV(std::vector<T> &res) {
    if constexpr (std::is_same<T, double>::value ) res.push_back(3.5);
    else if constexpr (std::is_same<T, int>::value) res.push_back(2);
    else if constexpr (std::is_same<T, std::string>::value) res.push_back(std::string("hello"));
}

LIVE

Before C++17, you can add some overloads processing different variants of std::vector. E.g.

void fill_v(std::vector<double>& res) { res.push_back(3.5); }
void fill_v(std::vector<int>& res) { res.push_back(2); }
void fill_v(std::vector<std::string>& res) { res.push_back(std::string("hello")); }

template <typename T>
void fillV(std::vector<T> &res) {
    if (std::is_same<T, double>::value ) fill_v(res);
    else if (std::is_same<T, int>::value) fill_v(res);
    else if (std::is_same<T, std::string>::value) fill_v(res);
    // or just 
    // fill_v(res);
}
Related