1
votes

I recently posted this as a possible bug in the Arduino forum (https://forum.arduino.cc/index.php?topic=656968), but I suspect they are going to "ho-hum, so what?" it because it goes outside of their standard rules for Arduino library organisation. However, I still want to understand why it is failing and I suspect this is the only place I will get decent and/or definitive answers:

( For the record, an arduino library normally sits in a folder which has a library.properties metadata "manifest" and a src folder containing all the files. Their build system automatically compiles (and links!) all the .cpp files in src - whether they are used or not. Obviously in 99% of cases all of them are used /needed, but for various reasons...)


BEGIN INCLUSION

Version 1.8.10 ESP8266 core 2.6.2

Before we start, I have written several several successful libraries and I am familiar with the "standard" way of doing things. I say this because what follows may be considered "non-standard" although it is syntactically valid and in all but one instance, compiles and runs successfully. I came across it by accident while experimenting to try and get round the restriction of compiling everything in src folder whether you want it or not

In the failing instance, I get a compilation error that I have never seen before,don't understand and makes no sense; but worse: it should not be happening at all as it is a) valid code b) functionally identical to the working cases - and compiles cleanly when not in a separate folder as is the current case...

The fundamental problem then is that the only significant difference between failure or success of this piece of valid C++ code is its physical location in the file system, which baffles me.

First the sketch:

#include <H4P_WiFiX.h>

void f1(){}
void f2(){}
/*
 * H4P_WiFi has defaults of [](){}, [](){}
 */
//H4P_WiFi h4wifi(f1,f2); // compiles OK
//H4P_WiFi h4wifi([](){},f2); // compiles OK
//H4P_WiFi h4wifi(f1,[](){}); // compiles OK
//H4P_WiFi h4wifi(f1); // compiles OK
//H4P_WiFi h4wifi([](){},[](){}); // compiles OK
//H4P_WiFi h4wifi([](){}); // compiles OK
H4P_WiFi h4wifi; // using BOTH defaults (and ONLY that case), fails with the "helpful":
/*
C:\Users\phil\AppData\Local\Temp\ccnwDwvr.s: Assembler messages:

C:\Users\phil\AppData\Local\Temp\ccnwDwvr.s:21: Error: symbol `_ZNSt17_Function_handlerIFvvEZN8H4P_WiFiC1ESt8functionIS0_ES3_Ed_NUlvE_EE9_M_invokeERKSt9_Any_data' is already defined

C:\Users\phil\AppData\Local\Temp\ccnwDwvr.s:97: Error: symbol `_ZNSt14_Function_base13_Base_managerIZN8H4P_WiFiC1ESt8functionIFvvEES4_Ed_NUlvE_EE10_M_managerERSt9_Any_dataRKS7_St18_Manager_operation' is already defined

*/

void setup() {}

void loop() {}

Now the bizarre stuff: The "odd" folder structure is:

libraries/H4Pwtf
|--optional
     |
     |--H4P_WiFi.cpp
     |--H4P_WiFi.h
     |--H4Plugins.h

!--src
     |
     |--H4P_WiFiX,h
     |--H4Plugins.cpp
library.properties

src/H4P_WiFiX.h

#ifndef H4P_WiFi2_H
#define H4P_WiFi2_H
// Yes, I know, I know! See above - it's valid even though weird
#include"../optional/H4P_WiFi.cpp"

#endif

src/H4Plugins.cpp

#include"../optional/H4Plugins.h"

H4PluginService::H4PluginService(H4P_FN_VOID onConnect,H4P_FN_VOID onDisconnect){}

optional/H4P_Plugins.h

#ifndef H4P_HO
#define H4P_HO

#include<functional>

using H4P_FN_VOID = std::function<void()>;

class H4Plugin {};

class H4PluginService: public H4Plugin {
    public:
        H4PluginService(H4P_FN_VOID onConnect=[](){},H4P_FN_VOID onDisconnect=[](){});
};

#endif // H4P_HO

optional/H4P_WiFi.h

#ifndef H4P_WiFi_HO
#define H4P_WiFi_HO


#include"H4Plugins.h"

class H4P_WiFi: public H4PluginService{
    public:
        H4P_WiFi(H4P_FN_VOID onConnect=[](){},H4P_FN_VOID onDisconnect=[](){});
};

#endif // H4P_WiFi_HO

optional/H4P_WiFi.cpp

#include"H4P_WiFi.h"

H4P_WiFi::H4P_WiFi(H4P_FN_VOID onC,H4P_FN_VOID onD): H4PluginService(onC,onD){}

Needless to say, if I move this all to src and edit the "weird" includes back to < > variants instead of " " versions and don't do the bizarre inclusion of .cpp (obvs because it will get compiled anyway by virtue of just "being there") then the line that shouldn't fail doesn't fail and everything compiles and runs 100% as expected.

But that's not the point: It looks to me as if the build system is including something twice when it shouldn't be...and I just cannot fathom that error! Any ideas?

END INCLUSION


So, overflowers: can anyone explain the bug and when it only happens when both defaults are used?


SOLVED thanks to "walnut". A residual issue is that he compiler bug only manifests when the two-identical-lambdas-as-defaults function is defined out-of-class. I moved it back inside, recompiled using my much-derided (but working) non-standard file organisation and: Bingo! Code now running exactly as expected and compiling in only #included modules. I'm a happy bunny. Thanks to others who contributed also. Case closed. +1 to stackoverflow

2
Sounds like there's another file with the same name that, if the conditions are right, gets included before one of your header files.Sam Varshavchik
symbols from of ../optional/H4P_WiFi.cpp will be in .o ofl all cpp which include the H4P_WiFiX.h.Juraj
There are no other files - what you see is the total problem - that's the pointPhil Bowles
Please don't add "SOLVED" to the title. The way to indicate that your question has been answered is to accept an answer. (You might want to upvote the answer too; that's up to you.)Keith Thompson
to achieve your goal include .cpp in .cppJuraj

2 Answers

1
votes

You can boil down the problematic code to the following minimal reproducible testcase, after substituting the #include directives and removing parts that do not affect the resulting error:

#include<functional>

using H4P_FN_VOID = std::function<void()>;

class H4P_WiFi{
    public:
        H4P_WiFi(H4P_FN_VOID onConnect=[](){}, H4P_FN_VOID onDisconnect=[](){});
};

H4P_WiFi::H4P_WiFi(H4P_FN_VOID, H4P_FN_VOID) {}

H4P_WiFi h4wifi;

int main() {}

Compiling this as a single compilation unit does indeed still generate the error messages with GCC, even on the most recent release 9.2, while Clang has no problem with it, see this godbolt.

The error messages GCC 9.2 gives during assembly are very similar to yours:

/tmp/cceVlRkg.s: Assembler messages:
/tmp/cceVlRkg.s:77: Error: symbol `_ZNSt14_Function_base13_Base_managerIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_E10_M_managerERSt9_Any_dataRKS7_St18_Manager_operation' is already defined
/tmp/cceVlRkg.s:131: Error: symbol `_ZNSt17_Function_handlerIFvvEZN8H4P_WiFiC4ESt8functionIS0_ES3_Ed_UlvE_E9_M_invokeERKSt9_Any_data' is already defined

Demangling the symbol names we get:

std::_Function_handler<void (), H4P_WiFi::H4P_WiFi(std::function<void ()>, std::function<void ()>)::{default arg#1}::{lambda()#1}>::_M_invoke(std::_Any_data const&)
std::_Function_base::_Base_manager<H4P_WiFi::H4P_WiFi(std::function<void ()>, std::function<void ()>)::{default arg#1}::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<H4P_WiFi::H4P_WiFi(std::function<void ()>, std::function<void ()>)::{default arg#1}::{lambda()#1}> const&, std::_Manager_operation)

I am pretty sure this is a compiler bug in GCC. On GCC trunk (on godbolt), we even get an ICE (internal compiler error):

<source>:12:16: error: Two symbols with same comdat_group are not linked by the same_comdat_group list.
   12 | H4P_WiFi h4wifi;
      |                ^
_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v/1079 (_Tp& std::_Any_data::_M_access() [with _Tp = H4P_WiFi::<lambda()>]) @0x7f019ddbd000
  Type: function definition analyzed
  Visibility: public weak comdat comdat_group:_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v one_only visibility_specified
  previous sharing asm name: 1078
  References: 
  Referring: 
  Function flags: body
  Called by: static void std::_Function_base::_Base_manager<_Functor>::_M_destroy(std::_Any_data&, std::true_type) [with _Functor = H4P_WiFi::<lambda()>]/1077 
  Calls: void* std::_Any_data::_M_access()/217 
_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v/1078 (_Tp& std::_Any_data::_M_access() [with _Tp = H4P_WiFi::<lambda()>]) @0x7f019ddb7e10
  Type: function definition analyzed
  Visibility: public weak comdat comdat_group:_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v one_only visibility_specified
  next sharing asm name: 1079
  References:
  Referring: 
  Function flags: body
  Called by: static void std::_Function_base::_Base_manager<_Functor>::_M_destroy(std::_Any_data&, std::true_type) [with _Functor = H4P_WiFi::<lambda()>]/1073 
  Calls: void* std::_Any_data::_M_access()/217 
<source>:12:16: internal compiler error: symtab_node::verify failed
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://gcc.gnu.org/bugs/> for instructions.
Compiler returned: 1

A similar bug has been reported in this GCC bug report. Apparently it only manifests for an out-of-class-defined member function of a class when called using at least two default arguments, each containing a lambda of equal signature.

Indeed if I e.g. add a parameter to one of the lambda's parameter list (e.g. [](auto...){} or [](int=0){}), but not the other ones, then there is no error. But if I add the same to both, we have again the same error.

0
votes

Most interactive build systems would create a compile module for each .cpp file unless explicitly instructed otherwise. I don't know an IDE which would not assume this as the only possible way. This would lead to breach of ODR rule, because HP4_WiFi module would contain same definitions as as HP4_Plugins module. In your case that is definition of that constructor.

Why it doesn't happen when constructor is not called? Undefined, but compiler likely just done some optimization and excluded unreferenced symbols.

Playing with <> and "" you likely introduce another uncertainity because that does nothing but to tell preprocesser to check default folders first instead of folder where including file is located. Your code may pick up something else from outside!

You either have to avoid including .cpp file (duh!) or, if reason to include it to be inlined (or templated), rename it to someting else, e.g. .inl.