一个库中的函数与另一个库中的模板相匹配
Function from one library matched to template from another library
我正在开发一个使用两个不同库的 C++ 项目:spdlog for logging, and mutils-serialization 用于将对象序列化为字节(用于通过网络发送)。这两个库都正确地使用了名称空间,但是当我尝试编写一个同时使用它们的程序时,我的编译器 (g++ 6.2) 给我一些荒谬的错误,这些错误似乎表明它正在尝试从 spdlog 库中实例化一个函数模板通过使用 mutils 库中的函数模板定义。
这是我的简单测试程序:
#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
是一个简单的 class,它实现了 mutils::ByteRepresentable
(启用序列化并引入 mutils-serialization 库的接口),并提供了一个 operator<<
(这是 spdlog 能够记录它所必需的)。如有必要,我可以 post 它的代码。
当我用 g++ -std=c++14 -I"./src" -I"./libraries" -I"./libraries/mutils/" -L"./libraries/" -O0 -g3 -Wall "src/LibraryCollisionTest.cpp"
编译它时,我得到了这个又长又难看的错误(别担心,我会帮你解析它):
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) };
关键行在这里:
./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]’
g++ 以某种方式从命名空间 fmt::internal
中的 spdlog 库中的模板函数扩展到命名空间 mutils
中的 mutils 库中的函数模板,这显然不是什么spdlog库打算做的!如果我查看 format.h
的第 1276 行,它就是调用此模板结构中的 "convert" 函数的行:
template<typename T>
struct ConvertToInt
{
enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) };
enum { value = ConvertToIntImpl2<T, enable_conversion>::value };
};
上面几行,果然是函数"convert":
template <typename T>
T &get();
Yes &convert(fmt::ULongLong);
No &convert(...);
这些都在命名空间fmt::internal
里面,我的IDE同意,如果我要1276行的函数"convert"的定义,应该跳转到函数"convert" 在第 1248 行。那么为什么 g++ 忽略这个定义,而是尝试使用甚至不在正确命名空间中的 mutils::convert()
的定义?
请注意,clang 也无法编译此程序,并且会出现同样的错误,因此我认为这不是 g++ 中的错误。
这绝对是 spdlog fmtlib 中的一个错误,由 spdlog 在内部使用。
问题 在此常见问题解答中进行了简要描述:
What is “Argument-Dependent Lookup” (aka ADL, or “Koenig Lookup”)?
因为 messaging::TestSerializableObject
继承自命名空间 mutils
中的类型,当 convert
从命名空间 fmt::internal
内部使用 TestSerializableObject
不合格地调用时,两者 fmt::internal::convert
和 mutils::convert
被认为在重载集中。可变参数函数在重载决策期间总是排在最后,因此后者的模板参数 F
比前者的 ...
更匹配,因此选择 mutils::convert
。
这绝不是您的代码或 mutils 所特有的——在同一名称空间或父名称空间中具有名为 convert
的一元函数或函数模板的任何类型都容易受到此问题的影响。
修复 是为了限定 convert
调用并将 fmt::internal::ConvertToInt<T>::enable_conversion
的定义从
更改为
enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) };
至
enum { enable_conversion = sizeof(internal::convert(get<T>())) == sizeof(Yes) };
在我自己的代码中,我习惯总是限定对任何 internal
/detail
命名空间内函数的所有调用,即使是来自同一命名空间内的代码,除非明确打算使用 ADL。 (N.b。调用不需要 完全 合格,只是合格。)我从观看 Boost 必须以 C+ 的艰难方式处理这个问题中吸取了这个教训+11 正在出现。
我正在开发一个使用两个不同库的 C++ 项目:spdlog for logging, and mutils-serialization 用于将对象序列化为字节(用于通过网络发送)。这两个库都正确地使用了名称空间,但是当我尝试编写一个同时使用它们的程序时,我的编译器 (g++ 6.2) 给我一些荒谬的错误,这些错误似乎表明它正在尝试从 spdlog 库中实例化一个函数模板通过使用 mutils 库中的函数模板定义。
这是我的简单测试程序:
#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
是一个简单的 class,它实现了 mutils::ByteRepresentable
(启用序列化并引入 mutils-serialization 库的接口),并提供了一个 operator<<
(这是 spdlog 能够记录它所必需的)。如有必要,我可以 post 它的代码。
当我用 g++ -std=c++14 -I"./src" -I"./libraries" -I"./libraries/mutils/" -L"./libraries/" -O0 -g3 -Wall "src/LibraryCollisionTest.cpp"
编译它时,我得到了这个又长又难看的错误(别担心,我会帮你解析它):
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) };
关键行在这里:
./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]’
g++ 以某种方式从命名空间 fmt::internal
中的 spdlog 库中的模板函数扩展到命名空间 mutils
中的 mutils 库中的函数模板,这显然不是什么spdlog库打算做的!如果我查看 format.h
的第 1276 行,它就是调用此模板结构中的 "convert" 函数的行:
template<typename T>
struct ConvertToInt
{
enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) };
enum { value = ConvertToIntImpl2<T, enable_conversion>::value };
};
上面几行,果然是函数"convert":
template <typename T>
T &get();
Yes &convert(fmt::ULongLong);
No &convert(...);
这些都在命名空间fmt::internal
里面,我的IDE同意,如果我要1276行的函数"convert"的定义,应该跳转到函数"convert" 在第 1248 行。那么为什么 g++ 忽略这个定义,而是尝试使用甚至不在正确命名空间中的 mutils::convert()
的定义?
请注意,clang 也无法编译此程序,并且会出现同样的错误,因此我认为这不是 g++ 中的错误。
这绝对是 spdlog fmtlib 中的一个错误,由 spdlog 在内部使用。
问题 在此常见问题解答中进行了简要描述:
What is “Argument-Dependent Lookup” (aka ADL, or “Koenig Lookup”)?
因为 messaging::TestSerializableObject
继承自命名空间 mutils
中的类型,当 convert
从命名空间 fmt::internal
内部使用 TestSerializableObject
不合格地调用时,两者 fmt::internal::convert
和 mutils::convert
被认为在重载集中。可变参数函数在重载决策期间总是排在最后,因此后者的模板参数 F
比前者的 ...
更匹配,因此选择 mutils::convert
。
这绝不是您的代码或 mutils 所特有的——在同一名称空间或父名称空间中具有名为 convert
的一元函数或函数模板的任何类型都容易受到此问题的影响。
修复 是为了限定 convert
调用并将 fmt::internal::ConvertToInt<T>::enable_conversion
的定义从
enum { enable_conversion = sizeof(convert(get<T>())) == sizeof(Yes) };
至
enum { enable_conversion = sizeof(internal::convert(get<T>())) == sizeof(Yes) };
在我自己的代码中,我习惯总是限定对任何 internal
/detail
命名空间内函数的所有调用,即使是来自同一命名空间内的代码,除非明确打算使用 ADL。 (N.b。调用不需要 完全 合格,只是合格。)我从观看 Boost 必须以 C+ 的艰难方式处理这个问题中吸取了这个教训+11 正在出现。