1

In the following code, I'm using a relatively simple type erasure technique. The class interpreter_context_ptr defines an "interface", and pointers to objects implementing the interface can be used to construct an interpreter_context_ptr. This allows polymorphism without using virtual dispatch.

This is extremely similar to an old article called Impossibly Fast Delegates. Also, check out the eraserface project by badair on github.

Also, if you don't recognize it at first, the +[](...) syntax here is the so-called "positive lambda" syntax, here's a good explanation.

Here's the MCVE:

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

class interpreter_context_ptr {
  void * object_;
  void (*new_text_call_)(void *, const std::string &);
  void (*error_text_call_)(void *, const std::string &);
  void (*clear_input_call_)(void *);

public:
  void new_text(const std::string & str) const {
    this->new_text_call_(object_, str);
  }

  void error_text(const std::string & str) const {
    this->error_text_call_(object_, str);
  }

  void clear_input() const { this->clear_input_call_(object_); }

  template <typename T>
  explicit interpreter_context_ptr(T * t)
    : object_(static_cast<void *>(t))
    , new_text_call_(+[](void * o, const std::string & str) {
      static_cast<T *>(o)->new_text(str);
    })
    , error_text_call_(+[](void * o, const std::string & str) {
      static_cast<T *>(o)->error_text(str);
    })
    , clear_input_call_(+[](void * o) { static_cast<T *>(o)->clear_input(); }) {
  }
};

/***
 * Tests
 */

struct A {
  void new_text(const std::string & str) {
    std::cout << "A: " << str << std::endl;
  }

  void error_text(const std::string & str) {
    std::cout << "A! " << str << std::endl;
  }
  void clear_input() { std::cout << std::endl; }
};

struct B {
  void new_text(const std::string & str) {
    std::cout << "B: " << str << std::endl;
  }

  void error_text(const std::string & str) {
    std::cout << "B! " << str << std::endl;
  }
  void clear_input() { std::cout << std::endl; }
};

int main() {
  std::vector<interpreter_context_ptr> stack;

  A a;
  B b;

  stack.emplace_back(&a);
  stack.back().new_text("1");
  stack.emplace_back(&b);
  stack.back().new_text("2");
  stack.emplace_back(&b);
  stack.back().new_text("3");
  stack.back().clear_input();
  stack.pop_back();
  stack.back().error_text("4");
  stack.emplace_back(&a);
  stack.back().error_text("5");
  stack.pop_back();
  stack.back().error_text("6");
  stack.pop_back();
  stack.back().new_text("7");

  stack.back().clear_input();
  stack.pop_back();
  std::cout << "Stack size = " << stack.size() << std::endl;
}

The code under test is extremely simple, just a few lines, and works well in gcc and clang in a project I worked on in the last few months.

However I get some quite terse error messages from MSVC which I don't understand.

First, it complains about the positive lambdas, which I think it shouldn't. (I am pasting these errors from rextester.com)

Error(s):

source_file.cpp(27): error C2061: syntax error: identifier 'T'
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(655): note: see reference to function template instantiation 'interpreter_context_ptr::interpreter_context_ptr<A>(T *)' being compiled
        with
        [
            T=A
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(773): note: see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,A>(_Objty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(773): note: see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,A>(_Objty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(918): note: see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,A>(std::allocator<_Ty> &,_Objty *,A &&)' being compiled
        with
        [
            _Alloc=std::allocator<interpreter_context_ptr>,
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(917): note: see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,A>(std::allocator<_Ty> &,_Objty *,A &&)' being compiled
        with
        [
            _Alloc=std::allocator<interpreter_context_ptr>,
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\vector(929): note: see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<interpreter_context_ptr,A>(_Ty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\vector(928): note: see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<interpreter_context_ptr,A>(_Ty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
source_file.cpp(68): note: see reference to function template instantiation 'void std::vector<interpreter_context_ptr,std::allocator<_Ty>>::emplace_back<A*>(A *&&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
source_file.cpp(68): note: see reference to function template instantiation 'void std::vector<interpreter_context_ptr,std::allocator<_Ty>>::emplace_back<A*>(A *&&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
source_file.cpp(28): error C2593: 'operator +' is ambiguous
source_file.cpp(28): note: could be 'built-in C++ operator+(void (__cdecl *)(void *,const std::string &))'
source_file.cpp(28): note: or       'built-in C++ operator+(void (__stdcall *)(void *,const std::string &))'
source_file.cpp(28): note: or       'built-in C++ operator+(void (__fastcall *)(void *,const std::string &))'
source_file.cpp(28): note: or       'built-in C++ operator+(void (__vectorcall *)(void *,const std::string &))'
source_file.cpp(28): note: while trying to match the argument list '(interpreter_context_ptr::<lambda_3268dba1ab087602b708c8fa2c92932b>)'
source_file.cpp(30): error C2061: syntax error: identifier 'T'
source_file.cpp(31): error C2593: 'operator +' is ambiguous
source_file.cpp(31): note: could be 'built-in C++ operator+(void (__cdecl *)(void *,const std::string &))'
source_file.cpp(31): note: or       'built-in C++ operator+(void (__stdcall *)(void *,const std::string &))'
source_file.cpp(31): note: or       'built-in C++ operator+(void (__fastcall *)(void *,const std::string &))'
source_file.cpp(31): note: or       'built-in C++ operator+(void (__vectorcall *)(void *,const std::string &))'
source_file.cpp(31): note: while trying to match the argument list '(interpreter_context_ptr::<lambda_b251fc93653023678ada88e17e2a71b3>)'
source_file.cpp(32): error C2061: syntax error: identifier 'T'
source_file.cpp(32): error C2593: 'operator +' is ambiguous
source_file.cpp(32): note: could be 'built-in C++ operator+(void (__cdecl *)(void *))'
source_file.cpp(32): note: or       'built-in C++ operator+(void (__stdcall *)(void *))'
source_file.cpp(32): note: or       'built-in C++ operator+(void (__fastcall *)(void *))'
source_file.cpp(32): note: or       'built-in C++ operator+(void (__vectorcall *)(void *))'
source_file.cpp(32): note: while trying to match the argument list '(interpreter_context_ptr::<lambda_3ba87b1970191b4772ddfa67a05f70ea>)'
source_file.cpp(28): error C2088: '+': illegal for class
source_file.cpp(31): error C2088: '+': illegal for class
source_file.cpp(32): error C2088: '+': illegal for class

Okay, something's wrong with the "positive lambda" theory in microsoft land, wherein the lambda gets implicitly converted to a function pointer and the unary operator + is a no-op. That's fine, let's get rid of the positive lambdas.

  template <typename T>
  explicit interpreter_context_ptr(T * t)
    : object_(static_cast<void *>(t))
    , new_text_call_([](void * o, const std::string & str) {
      static_cast<T *>(o)->new_text(str);
    })
    , error_text_call_([](void * o, const std::string & str) {
      static_cast<T *>(o)->error_text(str);
    })
    , clear_input_call_([](void * o) { static_cast<T *>(o)->clear_input(); }) {
  }

Turns out MSVC is still not happy -- the primary error is the cryptic C2061.

The compiler found an identifier where it wasn't expected. Make sure that identifier is declared before you use it.

An initializer may be enclosed by parentheses. To avoid this problem, enclose the declarator in parentheses or make it a typedef.

This error could also be caused when the compiler detects an expression as a class template argument; use typename to tell the compiler it is a type.

Here's the full log:

Error(s):

source_file.cpp(27): error C2061: syntax error: identifier 'T'
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(655): note: see reference to function template instantiation 'interpreter_context_ptr::interpreter_context_ptr<A>(T *)' being compiled
        with
        [
            T=A
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(773): note: see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,A>(_Objty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(773): note: see reference to function template instantiation 'void std::allocator<_Ty>::construct<_Objty,A>(_Objty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(918): note: see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,A>(std::allocator<_Ty> &,_Objty *,A &&)' being compiled
        with
        [
            _Alloc=std::allocator<interpreter_context_ptr>,
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\xmemory0(917): note: see reference to function template instantiation 'void std::allocator_traits<_Alloc>::construct<_Ty,A>(std::allocator<_Ty> &,_Objty *,A &&)' being compiled
        with
        [
            _Alloc=std::allocator<interpreter_context_ptr>,
            _Ty=interpreter_context_ptr,
            _Objty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\vector(929): note: see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<interpreter_context_ptr,A>(_Ty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\vector(928): note: see reference to function template instantiation 'void std::_Wrap_alloc<std::allocator<_Ty>>::construct<interpreter_context_ptr,A>(_Ty *,A &&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
source_file.cpp(68): note: see reference to function template instantiation 'void std::vector<interpreter_context_ptr,std::allocator<_Ty>>::emplace_back<A*>(A *&&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
source_file.cpp(68): note: see reference to function template instantiation 'void std::vector<interpreter_context_ptr,std::allocator<_Ty>>::emplace_back<A*>(A *&&)' being compiled
        with
        [
            _Ty=interpreter_context_ptr
        ]
source_file.cpp(30): error C2061: syntax error: identifier 'T'
source_file.cpp(32): error C2061: syntax error: identifier 'T'

I try their suggestion of putting typename everywhere before the T's, it doesn't help anything, and I don't get any more informative error messages.


What's the story here? What does the C2061 "identifier" error mean here, it means that MSVC can't keep track of the template parameter inside of the lambda body, or it is parsing it wrong and thinks T is a variable and not a type or something?

Is it impossible to refer to template parameters within a lambda which is inside a template function like this MSVC 2015?

Do I just have to factor out the lambdas and make more static template functions instead?


It turns out that if I just completely get rid of the lambdas and use template functions like so, then MSVC will compile it fine:

class interpreter_context_ptr {
  void * object_;
  void (*new_text_call_)(void *, const std::string &);
  void (*error_text_call_)(void *, const std::string &);
  void (*clear_input_call_)(void *);

  template <typename T>
  struct helper {
    static void new_text(void * o, const std::string & str) {
      static_cast<T*>(o)->new_text(str);
    }
    static void error_text(void * o, const std::string & str) {
      static_cast<T*>(o)->error_text(str);
    }
    static void clear_input(void * o) {
      static_cast<T*>(o)->clear_input();
    }
  };

public:
  void new_text(const std::string & str) const {
    this->new_text_call_(object_, str);
  }

  void error_text(const std::string & str) const {
    this->error_text_call_(object_, str);
  }

  void clear_input() const { this->clear_input_call_(object_); }

  template <typename T>
  explicit interpreter_context_ptr(T * t)
    : object_(static_cast<void *>(t))
    , new_text_call_(&helper<T>::new_text)
    , error_text_call_(&helper<T>::error_text)
    , clear_input_call_(&helper<T>::clear_input) {
  }
};

But as I wrote in comments, I'm pretty surprised if I actually have to go this far. The MSVC developers claim to have fully supported and implemented lambda functions on this C++11 feature page. I would think that should include being able to refer to ambient template parameters in the scope of the lambda.

Community
  • 1
  • 1
Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • You're using VS2015, which comes with the Clang/C2 compiler frontend. If you can't understand the error messages thrown at you by MSVC, try a different compiler. Note that Clang might accept this code whereas MSVC doesn't. Then you're out of luck. But if not, you're bound to get a clearer error message in any case. – rubenvb Nov 16 '16 at 09:49
  • Hmm, unfortunately, it seems at least real Clang and GCC accept this code without warnings, so my option might be a no go, although Clang/C2 does use the Microsoft C++ library, which may well be the cause of the error. If this is the case, it's a library bug. – rubenvb Nov 16 '16 at 10:16
  • Also, try just removing the `+` from the lambda's, I have read before MSVC chokes on that construct. The code compiles without them, but do check the behavior is the same. – rubenvb Nov 16 '16 at 10:18
  • @rubenvb: So I mean, how could it be the library? The `interpreter_context_ptr` class doesn't use the standard library here, save `std::string` and I don't think that's relevant, I think we could replace it with `int` (I can test this I guess.) Unless lambda function implementation somehow relies on the library in msvc I don't see where that could enter the picture, shouldn't this just be about core language features? – Chris Beck Nov 16 '16 at 10:22
  • well, it compiles when you remove the `+`es from the lambda's. SO I'm not sure where all the other errors are coming from. – rubenvb Nov 16 '16 at 10:28
  • Only the ambiguity errors go away when I remove the `+`, I still am getting the `identifier T` errors. It turns out that if I get rid of the lambdas entirely, it compiles fine on MSVC. But this is surprising if I actually have to do that, I thought lambdas were supposed to be a fully implemented and supported C++ feature in MSVC. This is C++11 lambdas, not C++14 generic lambdas or anything like that. – Chris Beck Nov 16 '16 at 10:29
  • if I copy paste the code from your question into http://webcompiler.cloudapp.net/, and remove the `+`, it compiles just fine. Do you have the latest VS2015 updates installed? "Supported" does not mean "Bug-free", by the way. You'll find that out sooner rather than later with MSVC. – rubenvb Nov 16 '16 at 10:37
  • Ah I remember this online compiler, I forgot how to find it. I was using rextester.com. I guess they might not have all the updates, I will try to check. Thanks for your time, if you post this as an answer I'll accept it. – Chris Beck Nov 16 '16 at 10:42

2 Answers2

1

At the time of this question, the Visual C++ 2015 compiler has trouble with operator+ on lambda's. Removing the + signs in front of each of your lambda's lets this compile. It does slightly alter the semantics (the effective type of the expression is slightly different), although I don't think it matters here.

Make sure you have the latest updates installed, to ensure no other compiler/library bugs make you lose time for no reason.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
1

MSVC has a problem with + in that it converts lambdas to more than one function pointer calling convention type; so + is ambiguous.

Usually dropping + will make your code "just work", as the implicit cast operators do the right thing and know about the calling convention of the pointer you are casting your lambda to.

That isn't your only problem. The next problem is that stateless lambdas aren't properly getting access to types available in the surrounding context.

MSVC 2015 is only nominally a C++11 compiler. It has many, many cases where it doesn't work right. Support for these kind of things tends to be much improved with updates to the compiler; ensure you have MSVC 2015 U3.1 (there was an U3, and then a patch on U3 that I call U3.1).

One more minor detail would be dealing with the case where T is a const type. I get lazy and replace the static_cast<void *>(t) with (void*)t, as it will both remove const and cast to void all in one step. Or you could const_cast<void*>(static_cast<const volatile void*>(t)) or somesuch.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524