2016-11-18 14 views
6

Pracuję nad projektem C++, który używa dwóch różnych bibliotek: spdlog do rejestrowania i mutils-serialization do serializacji obiektów do bajtów (do wysyłania przez sieć). Obie biblioteki używają przestrzeni nazw poprawnie, ale kiedy próbuję napisać program, który używa ich obu jednocześnie, mój kompilator (g ++ 6.2) podaje mi bezsensowne błędy, które zdają się wskazywać, że próbuje utworzyć szablon funkcji z biblioteki spdlog za pomocą definicji szablonu funkcji z biblioteki mutils.Funkcja z jednej biblioteki dopasowanej do szablonu z innej biblioteki

Oto mój prosty program testowy:

#include <spdlog/spdlog.h> 
#include <spdlog/fmt/ostr.h> 
#include "TestSerializableObject.h" 

int main(int argc, char** argv) { 
    auto global_logger = spdlog::rotating_logger_mt("global_logger", "log", 1024 * 1024 * 500, 3); 
    global_logger->set_pattern("[%H:%M:%S.%e] [%l] %v"); 
    global_logger->set_level(spdlog::level::trace); 

    std::shared_ptr<spdlog::logger> logger(spdlog::get("global_logger")); 

    auto message = std::make_shared<messaging::TestSerializableObject>(
     1, 2, "A message!"); 

    logger->trace("Received a message: {}", *message); 
} 

TestSerializableObject to prosta klasa, która implementuje mutils::ByteRepresentable (interfejs, który umożliwia szeregowanie i ciągnie w bibliotece mutils-serializacji) i zapewnia operator<< (co jest wymagane dla spdlog, aby móc się zalogować). W razie potrzeby mogę opublikować kod.

Kiedy skompilować to z g++ -std=c++14 -I"./src" -I"./libraries" -I"./libraries/mutils/" -L"./libraries/" -O0 -g3 -Wall "src/LibraryCollisionTest.cpp", otrzymuję ten długi, brzydki błąd (nie martw się, pomogę ci ją analizować):

In file included from ./libraries/mutils/mutils.hpp:3:0, 
       from ./libraries/mutils-serialization/SerializationSupport.hpp:2, 
       from src/TestSerializableObject.h:10, 
       from src/LibraryCollisionTest.cpp:10: 
./libraries/mutils/args-finder.hpp: In instantiation of ‘struct mutils::function_traits<messaging::TestSerializableObject>’: 
./libraries/mutils/args-finder.hpp:75:41: required from ‘auto mutils::convert(F) [with F = messaging::TestSerializableObject; ignore = void]’ 
./libraries/spdlog/fmt/bundled/format.h:1276:46: required from ‘struct fmt::internal::ConvertToInt<messaging::TestSerializableObject>’ 
./libraries/spdlog/fmt/bundled/format.h:1485:5: required by substitution of ‘template<class T> fmt::internal::MakeValue<Formatter>::MakeValue(const T&, typename fmt::internal::EnableIf<fmt::internal::Not<fmt::internal::ConvertToInt<T>::value>::value, int>::type) [with T = messaging::TestSerializableObject]’ 
./libraries/spdlog/fmt/bundled/format.h:2465:12: required from ‘static fmt::internal::Value fmt::internal::ArgArray<N, true>::make(const T&) [with Formatter = fmt::BasicFormatter<char>; T = messaging::TestSerializableObject; unsigned int N = 1u]’ 
./libraries/spdlog/fmt/bundled/format.h:2898:5: required from ‘void fmt::BasicWriter<Char>::write(fmt::BasicCStringRef<CharType>, const Args& ...) [with Args = {messaging::TestSerializableObject}; Char = char]’ 
./libraries/spdlog/details/logger_impl.h:69:9: required from ‘void spdlog::logger::log(spdlog::level::level_enum, const char*, const Args& ...) [with Args = {messaging::TestSerializableObject}]’ 
./libraries/spdlog/details/logger_impl.h:127:5: required from ‘void spdlog::logger::trace(const char*, const Args& ...) [with Args = {messaging::TestSerializableObject}]’ 
src/LibraryCollisionTest.cpp:21:53: required from here 
./libraries/mutils/args-finder.hpp:12:37: error: ‘operator()’ is not a member of ‘messaging::TestSerializableObject’ 
    : public function_traits<decltype(&T::operator())> 
            ^~ 
./libraries/mutils/args-finder.hpp: In instantiation of ‘auto mutils::convert(F) [with F = messaging::TestSerializableObject; ignore = void]’: 
./libraries/spdlog/fmt/bundled/format.h:1276:46: required from ‘struct fmt::internal::ConvertToInt<messaging::TestSerializableObject>’ 
./libraries/spdlog/fmt/bundled/format.h:1485:5: required by substitution of ‘template<class T> fmt::internal::MakeValue<Formatter>::MakeValue(const T&, typename fmt::internal::EnableIf<fmt::internal::Not<fmt::internal::ConvertToInt<T>::value>::value, int>::type) [with T = messaging::TestSerializableObject]’ 
./libraries/spdlog/fmt/bundled/format.h:2465:12: required from ‘static fmt::internal::Value fmt::internal::ArgArray<N, true>::make(const T&) [with Formatter = fmt::BasicFormatter<char>; T = messaging::TestSerializableObject; unsigned int N = 1u]’ 
./libraries/spdlog/fmt/bundled/format.h:2898:5: required from ‘void fmt::BasicWriter<Char>::write(fmt::BasicCStringRef<CharType>, const Args& ...) [with Args = {messaging::TestSerializableObject}; Char = char]’ 
./libraries/spdlog/details/logger_impl.h:69:9: required from ‘void spdlog::logger::log(spdlog::level::level_enum, const char*, const Args& ...) [with Args = {messaging::TestSerializableObject}]’ 
./libraries/spdlog/details/logger_impl.h:127:5: required from ‘void spdlog::logger::trace(const char*, const Args& ...) [with Args = {messaging::TestSerializableObject}]’ 
src/LibraryCollisionTest.cpp:21:53: required from here 
./libraries/mutils/args-finder.hpp:75:41: error: ‘as_function’ is not a member of ‘mutils::function_traits<messaging::TestSerializableObject>’ 
    return function_traits<F>::as_function(f); 
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~ 
In file included from ./libraries/spdlog/fmt/fmt.h:21:0, 
       from ./libraries/spdlog/common.h:41, 
       from ./libraries/spdlog/spdlog.h:12, 
       from src/LibraryCollisionTest.cpp:8: 
./libraries/spdlog/fmt/bundled/format.h: In instantiation of ‘struct fmt::internal::ConvertToInt<messaging::TestSerializableObject>’: 
./libraries/spdlog/fmt/bundled/format.h:1485:5: required by substitution of ‘template<class T> fmt::internal::MakeValue<Formatter>::MakeValue(const T&, typename fmt::internal::EnableIf<fmt::internal::Not<fmt::internal::ConvertToInt<T>::value>::value, int>::type) [with T = messaging::TestSerializableObject]’ 
./libraries/spdlog/fmt/bundled/format.h:2465:12: required from ‘static fmt::internal::Value fmt::internal::ArgArray<N, true>::make(const T&) [with Formatter = fmt::BasicFormatter<char>; T = messaging::TestSerializableObject; unsigned int N = 1u]’ 
./libraries/spdlog/fmt/bundled/format.h:2898:5: required from ‘void fmt::BasicWriter<Char>::write(fmt::BasicCStringRef<CharType>, const Args& ...) [with Args = {messaging::TestSerializableObject}; Char = char]’ 
./libraries/spdlog/details/logger_impl.h:69:9: required from ‘void spdlog::logger::log(spdlog::level::level_enum, const char*, const Args& ...) [with Args = {messaging::TestSerializableObject}]’ 
./libraries/spdlog/details/logger_impl.h:127:5: required from ‘void spdlog::logger::trace(const char*, const Args& ...) [with Args = {messaging::TestSerializableObject}]’ 
src/LibraryCollisionTest.cpp:21:53: required from here 
./libraries/spdlog/fmt/bundled/format.h:1276:38: warning: invalid application of ‘sizeof’ to a void type [-Wpointer-arith] 
    enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) }; 

Kluczem jest tutaj linia:

./libraries/mutils/args-finder.hpp: In instantiation of ‘auto mutils::convert(F) 
[with F = messaging::TestSerializableObject; ignore = void]’: 
./libraries/spdlog/fmt/bundled/format.h:1276:46: required from ‘struct 
fmt::internal::ConvertToInt<messaging::TestSerializableObject>’ 
./libraries/spdlog/fmt/bundled/format.h:1485:5: required by substitution of 
‘template<class T> fmt::internal::MakeValue<Formatter>::MakeValue(const 
T&, typename fmt::internal::EnableIf<fmt::internal::Not< 
fmt::internal::ConvertToInt<T>::value>::value, int>::type) [with T = 
messaging::TestSerializableObject]’ 

W jakiś sposób g ++ przeskoczyło od rozszerzenia funkcji szablonowej wewnątrz biblioteki spdlog, w przestrzeni nazw fmt::internal, do szablonu funkcji w bibliotece mutils, w przestrzeni nazw mutils, co oczywiście nie jest tym, co biblioteka spdlog zamierzała zrobić! Jeśli patrzę na linii 1276 z format.h, to taka, która wywołuje funkcję „Convert” wewnątrz tego szablonu struct:

template<typename T> 
struct ConvertToInt 
{ 
    enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) }; 
    enum { value = ConvertToIntImpl2<T, enable_conversion>::value }; 
}; 

Kilka linijek wyżej, na pewno wystarczy, to funkcja „przerobić”:

template <typename T> 
T &get(); 

Yes &convert(fmt::ULongLong); 
No &convert(...); 

To wszystko w przestrzeni nazw fmt::internal, a moje IDE zgadza się, że jeśli chcę definicji funkcji "przekonwertować" w linii 1276, powinienem przejść do funkcji "konwertuj" w linii 1248. Dlaczego więc g ++ zignorować to Definicja, a zamiast tego spróbuj użyć definicji dla mutils::convert(), której nie ma nawet we właściwej przestrzeni nazw?

Należy pamiętać, że clang również nie kompiluje tego programu i popełnia ten sam błąd, więc nie sądzę, że jest to błąd w g ++.

+2

Naprawiono comiited w spdlog (https://github.com/gabime/spdlog/commit/d6b34d7b5c62a0000d96939ad946b85a2274f2bb) – GabiMe

+0

Dzięki @GabiMe! Sprawdziłem nową wersję i rozwiązałem mój problem. – Edward

Odpowiedz

9

To jest zdecydowanie błąd w spdlog.

problemem jest opisane skrótowo w tym FAQ:
What is “Argument-Dependent Lookup” (aka ADL, or “Koenig Lookup”)?

ponieważ messaging::TestSerializableObject dziedziczy z typu w przestrzeni nazw mutils, gdy convert nazywa niewykwalifikowany od wewnątrz przestrzeni nazw fmt::internal z TestSerializableObject, zarówno fmt::internal::convertimutils::convert są rozpatrywane w zestawie przeciążeniowym. Funkcje Variadic zawsze znajdują się na ostatnim miejscu podczas rozwiązywania przeciążenia, więc argument szablonu F w tym ostatnim jest lepiej dopasowany niż w przypadku wybrania ... w pierwszym i.

Nie jest to w żaden sposób związane z kodem ani z mutils – każdy typ z jednokierunkową funkcją lub szablonem funkcji o nazwie convert w tej samej przestrzeni nazw lub nadrzędnej przestrzeni nazw jest podatny na ten problem.

fix jest zakwalifikowanie połączenia convert i zmienić definicję fmt::internal::ConvertToInt<T>::enable_conversion z

enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) }; 

do

enum { enable_conversion = sizeof(internal::convert(get<T>())) == sizeof(Yes) }; 

W moim kodu mam w zwyczaju zawsze kwalifikacyjna wszystkie połączenia działa wewnątrz dowolnej przestrzeni nazw internal/detail, nawet z kodu wewnątrz tej samej przestrzeni nazw, un mniej ADL jest wyraźnie przeznaczone. (Połączenia N.b. nie muszą być kwalifikowane w pełni, po prostu kwalifikowane.) Nauczyłem się tej lekcji z oglądania, jak Boost musiał poradzić sobie z tym problemem na własnej skórze, gdy pojawił się C++ 11. : -]

+2

Lub przenieś pomocników do klasy cech jako statycznych funkcji członka. Nie ma ADL, jeśli nazwa zostanie rozstrzygnięta na członka klasy. Można też nawiasować "konwertować", chociaż jest to prawdopodobnie zbyt subtelne. –

+0

@ T.C. : Tak, myślałem o wzmiance na temat nawiasów, ale to jedna z rzeczy, o których 90% programistów C++ nie wie. Zbyt subtelne. – ildjarn

+0

W ostateczności, gdy włącza się z oddzielnych bibliotek, zderzenie w następujący sposób: Czasami prostym rozwiązaniem jest użycie tylko jednej z dwóch bibliotek w każdym z własnych plików cpp. –

Powiązane problemy