简体   繁体   中英

type-erasure, delegates, and lambda functions, versus msvc

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 'sa 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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM