0

The following is an example of a class that iterates through an STL container and prints out each element, with the initialization code attached for the main().

It seems as if the compiler parses the entire ContainerPrinterClass at compile time and throws an error when it gets to a line that is unsupported by an element in one of the passed in STL containers, for example element.first/element.second for type int in the vector. Yet, it's not supposed to be even entering that condition because flow control clearly states to only enter it if (std::is_same<T, std::map<int, std::string>>::value) is true.

The opposite is also true for when the map is passed into the print function and the compiler flags the error in the else condition where it tries to print the element, which is a std::pair and can't. But it should not even be in that else condition, since if a map is passed it, execution goes to the if condition and skips the else entirely.

I've also attempted with trying to substitute the (typeid(*container) == typeid(std::map<int, std::string>)) and dynamic_cast to check if the element is a std::pair with same results, where the compiler parses all the conditions in the printer function (even the incorrect ones for a particular type) at compile time.

So does anyone know how to provide this kind of template/generic functionality akin to Java's instanceOf and not have the compiler go blindly parsing the code in the conditions it's not supposed to go into for that particular type?


// the printer class
class ContainterPrinterClass {
public:
ContainterPrinterClass() {};

template<class T> void printElementsInContainer(T *container) {
    // if container type is map, print key and value
    if (std::is_same<T, std::map<int, std::string>>::value) {
        for each (auto element in *container) {
            std::cout << "key: " << element.first << " val: " << element.second << std::endl;
        }
    } else {
        // else if container is a vector, print just element
        for each (auto element in *container) {
            std::cout << "val: " << element << std::endl;
        }
    }
}
};

// in main()
ContainterPrinterClass containerPrinterClass;

std::vector<int> vectorOfInts = {1};
containerPrinterClass.printElementsInContainer<std::vector<int>>(&vectorOfInts);

std::map<int, std::string> mapOfStrings = {std::pair<int, std::string>(2, "Two")};
containerPrinterClass.printElementsInContainer<std::map<int, std::string>>(&mapOfStrings);

This however it will not compile and is giving the following error:

1>e:\workbench\projects\visualstudio_workspaces\tuts\testbed1\basic_app\basic_app.cpp(503): error C2228: left of '.first' must have class/struct/union
1>          type is 'int'
1>          e:\workbench\projects\visualstudio_workspaces\tuts\testbed1\basic_app\basic_app.cpp(747) : see reference to function template instantiation 'void ContainterPrinterClass::printElementsInContainer<std::vector<int,std::allocator<_Ty>>>(T *)' being compiled
1>          with
1>          [
1>              _Ty=int
1>  ,            T=std::vector<int,std::allocator<int>>
1>          ]
1>e:\workbench\projects\visualstudio_workspaces\tuts\testbed1\basic_app\basic_app.cpp(503): error C2228: left of '.second' must have class/struct/union
1>          type is 'int'
1>e:\workbench\projects\visualstudio_workspaces\tuts\testbed1\basic_app\basic_app.cpp(508): error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'std::pair<const _Kty,_Ty>' (or there is no acceptable conversion)
1>          with
1>          [
1>              _Kty=int
1>  ,            _Ty=std::string
1>          ]
1>          c:\visualstudio2013pro\vc\include\ostream(498): could be 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(std::basic_streambuf<char,std::char_traits<char>> *)'
1>          c:\visualstudio2013pro\vc\include\ostream(478): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(const void *)'
1>          c:\visualstudio2013pro\vc\include\ostream(458): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(long double)'
1>          c:\visualstudio2013pro\vc\include\ostream(438): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(double)'
1>          c:\visualstudio2013pro\vc\include\ostream(418): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(float)'
1>          c:\visualstudio2013pro\vc\include\ostream(397): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned __int64)'
1>          c:\visualstudio2013pro\vc\include\ostream(377): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(__int64)'
1>          c:\visualstudio2013pro\vc\include\ostream(356): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned long)'
1>          c:\visualstudio2013pro\vc\include\ostream(336): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(long)'
1>          c:\visualstudio2013pro\vc\include\ostream(316): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned int)'
1>          c:\visualstudio2013pro\vc\include\ostream(291): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(int)'
1>          c:\visualstudio2013pro\vc\include\ostream(271): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned short)'
1>          c:\visualstudio2013pro\vc\include\ostream(237): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(short)'
1>          c:\visualstudio2013pro\vc\include\ostream(217): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(std::_Bool)'
1>          c:\visualstudio2013pro\vc\include\ostream(210): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(std::ios_base &(__cdecl *)(std::ios_base &))'
1>          c:\visualstudio2013pro\vc\include\ostream(203): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(std::basic_ios<char,std::char_traits<char>> &(__cdecl *)(std::basic_ios<char,std::char_traits<char>> &))'
1>          c:\visualstudio2013pro\vc\include\ostream(197): or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(std::basic_ostream<char,std::char_traits<char>> &(__cdecl *)(std::basic_ostream<char,std::char_traits<char>> &))'
1>          c:\visualstudio2013pro\vc\include\ostream(699): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)'
1>          c:\visualstudio2013pro\vc\include\ostream(746): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char)'
1>          c:\visualstudio2013pro\vc\include\ostream(784): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)'
1>          c:\visualstudio2013pro\vc\include\ostream(831): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char)'
1>          c:\visualstudio2013pro\vc\include\ostream(957): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const signed char *)'
1>          c:\visualstudio2013pro\vc\include\ostream(964): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,signed char)'
1>          c:\visualstudio2013pro\vc\include\ostream(971): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const unsigned char *)'
1>          c:\visualstudio2013pro\vc\include\ostream(978): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,unsigned char)'
1>          c:\visualstudio2013pro\vc\include\ostream(988): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>,std::pair<const _Kty,_Ty>>(std::basic_ostream<char,std::char_traits<char>> &&,const std::pair<const _Kty,_Ty> &)'
1>          with
1>          [
1>              _Kty=int
1>  ,            _Ty=std::string
1>          ]
1>          c:\visualstudio2013pro\vc\include\ostream(1026): or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const std::error_code &)'
1>          while trying to match the argument list '(std::basic_ostream<char,std::char_traits<char>>, std::pair<const _Kty,_Ty>)'
1>          with
1>          [
1>              _Kty=int
1>  ,            _Ty=std::string
1>          ]
1>          e:\workbench\projects\visualstudio_workspaces\tuts\testbed1\basic_app\basic_app.cpp(750) : see reference to function template instantiation 'void ContainterPrinterClass::printElementsInContainer<std::map<int,std::string,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>>>(T *)' being compiled
1>          with
1>          [
1>              _Kty=int
1>  ,            _Ty=std::string
1>  ,            T=std::map<int,std::string,std::less<int>,std::allocator<std::pair<const int,std::string>>>
1>          ]
Sharik
  • 3
  • 2
  • 1
    Why not just provide a specialization of `printElementsInContainer` for `std::map`? – user657267 Nov 04 '14 at 03:40
  • Note that `for each` is a Visual C++ extension to C++ and is not standard. If you are looking for the C++11 container iteration loop structure, it is `for (auto & element : *container)`. (But why aren't you passing your container by reference?) – cdhowie Nov 04 '14 at 03:41
  • When compiling, all code paths must contain valid code, so trying to access the members `first` and `second` is not going to compile when the function template is specialized for `std::vector`. You can tag dispatch based on `true_type/false_type` that `std::is_same` yields to implementations that handle the `map` and `vector` cases separately. Easiest solution is to use the [pretty printer](http://stackoverflow.com/questions/4850473/pretty-print-c-stl-containers) – Praetorian Nov 04 '14 at 03:42
  • `if` is not a conditional code compilation tool. – chris Nov 04 '14 at 03:43

2 Answers2

2

When a function template is instantiated all of its code need to be correct code. When you want to execute code which depends on capabilities of the template parameters you need to dispatch to suitable function template taking advantage of the respect abilities. For example, you can rewrite your code like this:

template<class T>
typename std::enable_if<std::is_same<T, std::map<int, std::string>>::value>::type
printElementsInContainer(T *container) {
    for (auto&& element: *container) {
        std::cout << "key: " << element.first << " val: " << element.second << '\n';
    }
}
template<class T>
typename std::enable_if<!std::is_same<T, std::map<int, std::string>>::value>::type
printElementsInContainer(T *container) {
    for (auto&& element: *container) {
        std::cout << "val: " << element << '\n';
    }
}

To some extend, the use of std::enable_if<...> does the corresponding type conditional.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Thank you for your answers guys, they're all valid options, however I must choose Dietmar's answer as the best as his answer points to std::enable which gives me exactly what I'm looking for aka "This is useful to hide signatures on compile time when a particular condition is not met" as it says in the documentation! – Sharik Nov 04 '14 at 04:01
  • @sharik: I think something like C++ has much to learn wherever you stand: I'm programming C++ since nearly 25 years and right now I'm sitting in a session at the C++ committee meeting - ... and I'm still learning! (today's major lesson for me was from the [`std::void_t<...>` proposal](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3911.pdf)). – Dietmar Kühl Nov 04 '14 at 04:04
1

This fails because even though std::is_same is going to return the same value for any one particular T, the other branch of the conditional still needs to be compiled.

This means that when you call with std::vector<int>, even though the std::is_same expression evaluates to false, the if body still needs to be correct, and int doesn't have a first member.

The same applies in the map case; the else body still needs to compile, and it doesn't because there isn't an operator<< that accepts std::pair<const int, std::string>.

Either use overloads, template specialization, or SFINAE to activate only the correct code. Here is an example using specialization:

template<class T> void printElementsInContainer(T *container) {
    for each (auto element in *container) {
        std::cout << "val: " << element << std::endl;
    }
}

template<> void printElementsInContainer(std::map<int, std::string> *container) {
    for each (auto element in *container) {
        std::cout << "key: " << element.first << " val: " << element.second << std::endl;
    }
}

I would instead consider combining overloading with your main function; you can separate the logic of iterating (which is the same for all containers) from the logic of displaying elements (which depends on the type of element in each container).

This code will work with any std::map (as long as the key and value can be written to a stream), and also std::multimap, std::unordered_map, or in general any standard (or custom) container whose elements are std::pair. It will also work with any container whose elements are not pairs but can be written to streams, such as your std::vector<int>.

template<class TStream, class TElement>
void writeElementTo(TStream & stream, TElement const & element)
{
    stream << "val: " << element;
}

template<class TStream, class TKey, class TValue>
void writeElementTo(TStream & stream, std::pair<TKey, TValue> const & element)
{
    stream << "key: " element.first << "value: " << element.second;
}

template<class T> void printElementsInContainer(T *container) {
    for each (auto element in *container) {
        writeElementTo(std::cout, element);
        std::cout << std::endl;
    }
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300