4

My title might be wrong - if so, please do correct me, but at some point its hard for me to keep track of what meta thingy I'm actually trying to achieve ;)

I have a class function template like this:

template<template<typename...> class MapType>
    Expression Expression::substitute(MapType<std::string, Expression> const& identifierToExpressionMap) const {
        return SubstitutionVisitor<MapType>(identifierToExpressionMap).substitute(something);
    }

The important part is the MapType. The idea is to allow either std::map or std::unordered_map to be plugged in at will. With GCC and Clang, this works, but Visual Studio 2013 throws a compilation error:

error C2664: 'Expression Expression::substitute<std::map>(const MapType &) const' :

cannot convert argument 1 from 'std::map<std::string,Expression,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>>' to 'const std::map &'

1>          with
1>          [
1>              MapType=std::map
1>          ]
1>          and
1>          [
1>              _Kty=std::string
1>  ,            _Ty=Expression
1>          ]

1>          Reason: cannot convert from 'std::map<std::string,Expression,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>>' to 'const std::map'

1>          with
1>          [
1>              _Kty=std::string
1>  ,            _Ty=Expression
1>          ]

1>          No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

It seems that MSVC does not put MapType and <std::string, Expression> together as a type, am I missing something here?

So my question is

  1. is this possible and just a bug in MSVC, or
  2. is there an easier way to do this. Please keep in mind that there are many other classes which receive the MapType parameter and instantiate their own version with different key/value Types.
Casey
  • 41,449
  • 7
  • 95
  • 125
ThE_-_BliZZarD
  • 722
  • 2
  • 12
  • 26
  • 3
    When MSVC disagrees with GCC and Clang, it's a bug in MSVC. – Casey Apr 16 '14 at 20:30
  • 1
    Unfortunately I don't have VS2013 to test on right now but since there aren't many replies, heres something you could test: flesh out the parameters on the function substitute to have the 2 default parameters for map's template (std::less and std::allocator). Then have the caller of substitute try passing in a map which has all its template parameters specified (ie: it has the std::less and std::allocator). The test here is to see whether VS handles better if all template parameters are explicitly present at every step. – qeadz Apr 16 '14 at 20:58
  • @qeadz But unordered_map has more template parameters than map - and I can _not_ pass std::map with all parameters specified into substitute, because then its not a template anymore AND I need different key/Value Types in the classes - maybe I dont get what you are proposing...? – ThE_-_BliZZarD Apr 16 '14 at 21:20
  • @qeadz I think you mentioned an important issue: The `std::map` class template actually has four template parameters, so when used as a template *template-argument*, it should require *all four* template arguments to instantiate (since default arguments are not passed to a template *template-parameter*). Yet, some examples of this compile with clang++ and g++, not sure why. – dyp Apr 16 '14 at 21:30
  • @qeadz Take a look at [this weird program](http://coliru.stacked-crooked.com/a/89c02b58eef551d0) which compiles with clang++ and g++ but not with MSVC2013 Update 1. I frankly don't quite understand *why* it compiles with clang++ and g++; somehow they've access to the default template arguments, it seems. – dyp Apr 16 '14 at 21:42
  • @ThE_-_BliZZarD yeah I know it's not going to resolve your issue but what I was thinking is that it'd draw a dividing line in the sand. That line is: is it because VS wants the exact same template parameter count all the way through. If it is then we're in 'look for a workaround' land. If it isn't then maybe it's possible to still get things working the way you'd like. – qeadz Apr 16 '14 at 21:48
  • 3
    Wow... this seems to be an open issue: http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_active.html#150 – dyp Apr 16 '14 at 21:51
  • @dyp I think we both wonder why that code your linked works on some but not other compilers which are all supposedly standards compliant for at least this stuff. However to me the code makes sense. I looked at it and thought 'surely it would output 4' and it does under the compilers which accept the code. – qeadz Apr 16 '14 at 21:53
  • Probably related to: http://stackoverflow.com/q/13521163/420683 – dyp Apr 16 '14 at 22:01

2 Answers2

4

It probably boils down to this:

template<class T, class U = int>
struct cat {};

template< template<class...> class Animal >
void foo()
{
    Animal<int> x; (void)x; // (A)
}

int main()
{
    foo<cat>();
}

This little innocent-looking program is accepted by clang++ and g++, but not by MSVC2013 Update 1.


Line (A) is the problematic one: I think in this example it's clear that the template template-parameter Animal should have two template parameters, when passing the class template cat.

It seems that clang++ and g++ support the use of default template arguments for this template template-parameter in line (A), while MSVC does not.

I do not know if the Standard requires either of them; see for example

It certainly appears to me that supporting the default template arguments is useful, since (as said in the active issue) the class templates of the Standard Library may have additional template parameters (with default arguments); and you'd need to know those implementation-details if you wanted to use them (directly) as template template-arguments.

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
1

Given the discussion which has happened I think maybe it would be time to consider a work-around. Since deducing the template template arguments seems to be the issue at hand, perhaps relaxing the requirements for MapType might be helpful.

template< typename MapType>
Expression Expression::substitute( MapType const& identifierToExpressionMap) const {
    return SubstitutionVisitor<MapType>(identifierToExpressionMap).substitute(something);
}

The caller is going to be compiled code calling a function and so the compiler will deduce the template arguments so basically you're pushing the responsibility of using the proper std::map or std::unordered_map onto the caller.

Now at least for most cases this may work. However it is possible to pass in some kind of container which compiles but doesn't actually have the right types. So ideally you'd still be wanting some kind of compile-time check to ensure MapType is supported (ie: either a std::map or std::unordered_map).

This could be via Boost concepts, or even just having two templated alias declarations - using enable_if you can ensure that the alias declaration is only available in two flavors: map or unordered_map.

qeadz
  • 1,476
  • 1
  • 9
  • 17
  • I accepted the other answer because there the actual reasons and problems are described, so for anyone having similar trouble that might me more useful. Sorry for that, you both gave good insights. – ThE_-_BliZZarD Apr 17 '14 at 16:00