【问题标题】:Compile-time detection of missing user-defined to_string()缺少用户定义的 to_string() 的编译时检测
【发布时间】:2020-06-01 19:00:50
【问题描述】:

我想为我创建的每个对象类型提供一个to_string(obj) 函数。 我找到了this question,应用了accepted answer,它可以工作。到目前为止一切顺利。

然后我创建了一个新类型,但忘记为它写一个to_string()(或者更好:我不小心使它无法被 ADL 访问)。问题是:我的程序仍然编译良好​​strong>,并且在运行时我得到一个模糊的堆栈溢出(TM)。

有没有办法获取合理的错误信息?

这是一个演示问题的小程序:notstd::to_string()notstd::adl_helper::as_string() 之间的无限递归。

#include <iostream>
#include <string>

namespace notstd {
  namespace adl_helper {
    using std::to_string;

    template<class T>
    std::string as_string( T&& t ) {
      return to_string( std::forward<T>(t) );
    }
  }
  template<class T>
  std::string to_string( T&& t ) {
    std::cout << "called" << std::endl; // <-- this is to show what's going on
    return adl_helper::as_string(std::forward<T>(t));
  }

  class A {
    /* both versions are needed, or the perfect forwarding candidate will
     * always be chosen by the compiler in case of a non-perfect match */
    //friend std::string to_string(A &a) { return std::string("a"); }
    //friend std::string to_string(const A &a) { return std::string("a"); }
  };
}


int main(int argc, char** argv) {

  notstd::A a;

  std::cout << to_string(a) << std::endl;
}

我尝试创建一个包装函数,该函数接受一个额外的参数,用于执行反递归检查,如下所示:

#include <iostream>
#include <string>
#include <cassert>

namespace notstd {
  namespace wrap_std {
    std::string to_string(double v, bool) { return std::to_string(v); }
    /* .... etc.....  */
  }

  namespace adl_helper {
    using wrap_std::to_string;

    template<class T>
    std::string as_string( T&& t ) {
      return to_string( std::forward<T>(t), true );
    }
  }
  template<class T>
  std::string to_string( T&& t, bool recurring = false ) {
    std::cout << "called" << std::endl;
    assert(!recurring);
    return adl_helper::as_string(std::forward<T>(t));
  }

  class A {
    /* both versions are needed, or the perfect forwarding candidate will
     * always be chosen by the compiler in case of a non-perfect match */
    //friend std::string to_string(A &a) { return std::string("A"); }
    //friend std::string to_string(const A &a) { return std::string("A"); }
  };
}


int main(int argc, char** argv) {

  notstd::A a;

  std::cout << to_string(a) << std::endl;
}

这里的问题是:

  • 我必须包装 all std::to_string() 重载
  • 我只会得到一个运行时错误,但我觉得这个问题可以而且应该在广告编译时检测到
  • 我可能会增加一些开销,仅在开发期间有用:也许我可以添加一些宏来在发布模式下停用所有这些,但它会增加更多工作

也许我可以使用模板来包装std::to_string() 并为我的类型创建特化......这将是一个完全不同的野兽,但如果合适的特化不可用,至少它会提供编译时错误。如果我理解得很好,我将再次包装所有 std::to_string() 重载,并且我可能不得不(几乎)忘记 ADL,至少在所有编译器都支持 c++20 之前。

谁有更好的解决方案?

谢谢!

【问题讨论】:

    标签: c++ c++11 tostring argument-dependent-lookup infinite-recursion


    【解决方案1】:

    接受answer 的想法不同:您将A notstd 命名空间,然后使用合格的notstd::to_string 而不是不合格的to_string。那就是:

    namespace notstd {
        // ...
    }
    
    class A {
        friend std::string to_string(const A&);
    };
    
    A a;
    std::cout << notstd::to_string(a);
    

    现在如果没有friend 函数,您的代码将无法编译。此外,您只需要一个友元函数(取const A&amp;),因为notstd::to_string(T&amp;&amp;) 不会出现在adl_helper::as_string(T&amp;&amp;) 内部的重载集中。

    A 放入notstd 会搞砸一切。您有无限递归问题,并且您需要两个朋友来处理 Aconst A 的情况在存在 notstd::to_string(T&amp;&amp;) 候选人的情况下:如果只定义了一个朋友,那么该候选人在其中一种情况下是更好的匹配,因为 @应添加/删除 987654336@ 限定符以调用朋友功能。

    【讨论】:

    • 那么,基于这个想法,我应该将to_string(std::vector)to_string(std::array)添加到std命名空间?
    • @davnat,您不能在std 中添加任何内容,除非标准明确允许这样做。但是您可以将to_string(std::vector/array) 添加到adl_helper。您也可以将这些函数添加到其他命名空间(my)中,然后在using std::to_string; 之后添加using my::to_string;
    • 这是有道理的,我认为第二种选择是最好的选择。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 2019-06-18
    • 2017-12-04
    • 1970-01-01
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    • 2016-04-05
    • 1970-01-01
    相关资源
    最近更新 更多