【问题标题】:Function from one library matched to template from another library一个库中的函数与另一个库中的模板匹配
【发布时间】:2016-11-18 21:11:21
【问题描述】:

我正在开发一个使用两个不同库的 C++ 项目:spdlog 用于日志记录,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 是一个简单的类,它实现了mutils::ByteRepresentable(启用序列化并引入mutils-serialization 库的接口),并提供了一个operator&lt;&lt;(spdlog 能够记录它所必需的) .如有必要,我可以发布它的代码。

当我用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++ 已经从扩展 spdlog 库中的模板函数(在命名空间 fmt::internal 中)到 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”,我应该跳转到第1248行的函数“convert”。那为什么g++忽略这个定义,而是尝试使用mutils::convert() 的定义,它甚至不在正确的命名空间中?

请注意,clang 也无法编译这个程序,并且犯了同样的错误,所以我认为这不是 g++ 中的错误。

【问题讨论】:

标签: c++ templates c++14 spdlog


【解决方案1】:

这绝对是 spdlog fmtlib 中的一个错误,由 spdlog 内部使用。

问题在此常见问题解答中进行了简要描述:
What is “Argument-Dependent Lookup” (aka ADL, or “Koenig Lookup”)?

因为messaging::TestSerializableObject 继承自命名空间mutils 中的一个类型,所以当convert 从内部命名空间fmt::internalTestSerializableObject 不合格调用时,fmt::internal::convert 和 @987654328 @ 被考虑在重载集中。可变参数函数在重载解析期间总是排在最后,因此后者中的模板参数F 比前者中的... 匹配更好,并且选择了mutils::convert

这绝不是您的代码或 mutils 特有的——在同一命名空间或父命名空间中具有名为 convert 的一元函数或函数模板的任何类型都容易受到此问题的影响。

修复是限定convert调用并将fmt::internal::ConvertToInt&lt;T&gt;::enable_conversion的定义从

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

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

在我自己的代码中,我习惯于始终限定对任何 internal/detail 命名空间内的所有函数调用,即使来自同一命名空间内的代码,除非明确打算使用 ADL。 (Nb 调用不需要完全合格,只要合格。)我从看到 Boost 在 C++11 出现时必须以艰难的方式处理这个问题中学到了这一课。

【讨论】:

  • 或者将助手作为静态成员函数移动到特征类中。如果名称解析为类成员,则没有 ADL。也可以用括号括起来 convert,虽然这可能太微妙了。
  • @T.C. : 是的,我想过提到括号的可能性,但这是 90% 的 C++ 开发人员不知道的事情之一。确实太微妙了。
  • 作为最后的手段,当来自不同库的包含发生这样的冲突时:有时在您自己的每个 cpp 文件中仅使用两个库中的一个是一种简单的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-19
相关资源
最近更新 更多