Repository: CLIUtils/CLI11 (C++)

Issue:
String-like conversion
In previous versions of CLI11, string-like types (Such as ROOT's TString) could be directly used in `add_option`. This now fails:

```cpp
In file included from /home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/CLI.hpp:29:0,
1533                 from /home/travis/build/GooFit/GooFit/include/goofit/Application.h:5,
1534                 from /home/travis/build/GooFit/GooFit/examples/TDDP4/TDDP4.cu:7:
1535/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp: In instantiation of ‘CLI::Option* CLI::App::add_option(std::__cxx11::string, AssignTo&, std::__cxx11::string, bool) [with AssignTo = TString; ConvertTo = TString; typename std::enable_if<(! std::is_const<ConvertTo>::value), CLI::detail::enabler>::type <anonymous> = (CLI::detail::type)0; std::__cxx11::string = std::__cxx11::basic_string<char>]’:
1536/home/travis/build/GooFit/GooFit/examples/TDDP4/TDDP4.cu:30:72:   required from here
1537/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:600:17: error: no matching function for call to ‘CLI::App::add_option(std::__cxx11::string&, CLI::App::add_option(std::__cxx11::string, AssignTo&, std::__cxx11::string, bool) [with AssignTo = TString; ConvertTo = TString; typename std::enable_if<(! std::is_const<ConvertTo>::value), CLI::detail::enabler>::type <anonymous> = (CLI::detail::type)0; std::__cxx11::string = std::__cxx11::basic_string<char>]::<lambda(const results_t&)>&, std::__cxx11::string&, bool&, CLI::App::add_option(std::__cxx11::string, AssignTo&, std::__cxx11::string, bool) [with AssignTo = TString; ConvertTo = TString; typename std::enable_if<(! std::is_const<ConvertTo>::value), CLI::detail::enabler>::type <anonymous> = (CLI::detail::type)0; std::__cxx11::string = std::__cxx11::basic_string<char>]::<lambda()>)’
1538         Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
1539                 ^~~
1540/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:546:13: note: candidate: CLI::Option* CLI::App::add_option(std::__cxx11::string, CLI::callback_t, std::__cxx11::string, bool, std::function<std::__cxx11::basic_string<char>()>)
1541     Option *add_option(std::string option_name,
1542             ^~~~~~~~~~
1543/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:546:13: note:   no known conversion for argument 5 from ‘CLI::App::add_option(std::__cxx11::string, AssignTo&, std::__cxx11::string, bool) [with AssignTo = TString; ConvertTo = TString; typename std::enable_if<(! std::is_const<ConvertTo>::value), CLI::detail::enabler>::type <anonymous> = (CLI::detail::type)0; std::__cxx11::string = std::__cxx11::basic_string<char>]::<lambda()>’ to ‘std::function<std::__cxx11::basic_string<char>()>’
1544/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:591:13: note: candidate: template<class AssignTo, class ConvertTo, typename std::enable_if<(! std::is_const<ConvertTo>::value), CLI::detail::enabler>::type <anonymous> > CLI::Option* CLI::App::add_option(std::__cxx11::string, AssignTo&, std::__cxx11::string, bool)
1545     Option *add_option(std::string option_name,
1546             ^~~~~~~~~~
1547/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:591:13: note:   template argument deduction/substitution failed:
1548/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:600:17: note:   candidate expects 4 arguments, 5 provided
1549         Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
1550                 ^~~
1551/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:637:13: note: candidate: CLI::Option* CLI::App::add_option(std::__cxx11::string)
1552     Option *add_option(std::string option_name) {
1553             ^~~~~~~~~~
1554/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:637:13: note:   candidate expects 1 argument, 5 provided
1555/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:645:13: note: candidate: template<class T, typename std::enable_if<(std::is_const< <template-parameter-1-1> >::value && std::is_constructible<std::__cxx11::basic_string<char>, T>::value), CLI::detail::enabler>::type <anonymous> > CLI::Option* CLI::App::add_option(std::__cxx11::string, T&)
1556     Option *add_option(std::string option_name, T &option_description) {
1557             ^~~~~~~~~~
1558/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:645:13: note:   template argument deduction/substitution failed:
1559/home/travis/build/GooFit/GooFit/extern/CLI11/include/CLI/App.hpp:600:17: note:   candidate expects 2 arguments, 5 provided
1560         Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() {
1561                 ^~~
1562examples/TDDP4/CMakeFiles/TDDP4.dir/build.make:62: recipe for target 'examples/TDDP4/CMakeFiles/TDDP4.dir/TDDP4.cu.o' failed
```

@phlptp, is this a side effect of the various optional and variant support changes? Is it necessary?


Discussion:
I am guessing it is an unintentional side effect,  not entirely sure exactly what aspect.   Not familiar with that type.  So I can't say with certainty what specifically is causing the problems.  We would probably need a failing test in CLI11 to really nail it down.  

My guess is you can use the two argument template if you can assign to a Tstring from std::string.  otherwise as best as I can tell the error basically says it can't match to one of the templates, which could be for any number of reasons down the chain.    I am guessing it is fixable but would need to know the details of Tstring.  
Interestingly, this works if you set up a simple test:

```bash
docker run --rm -v $PWD:/CLI11 -it rootproject/root:latest bash
cd CLI11/
$(root-config --cxx) $(root-config --cflags) $(root-config --libs) -I/CLI11/include examples/root.cpp
```

Works with root.cpp:

```cpp
#include <CLI/CLI.hpp>
#include <TString.h>
#include <iostream>

int main(int argc, char **argv) {

    CLI::App app("TString example");

    TString file;
    CLI::Option *opt = app.add_option("-f,--file,file", file, "File name");

    CLI11_PARSE(app, argc, argv);

    std::cout << "Working on file: " << file << std::endl;
    return 0;
}
```

~~So maybe there's something new I need to export from the CLI namespace to the GooFit namespace in my customized version.~~

~~I think it might be the final "true" that's causing the problem.~~

TString is here: https://root.cern.ch/doc/master/classTString.html
If I export the CLI namespace to the new namespace, I get a second problem: I'm using `rang` for colors, and now I get an ambigious overload error:

```
/Users/henryiii/git/fitting/goofit/src/goofit/Application.cpp:35:28: error: use of overloaded operator '<<' is ambiguous (with operand types 'std::__1::basic_ostream<char>' and 'const rang::style')
    std::cout << std::endl << reset << red << bold;
    ~~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~~
/Users/henryiii/git/fitting/goofit/extern/CLI11/include/CLI/StringTools.hpp:23:15: note: candidate function [with T = rang::style, $1 = void]
std::ostream &operator<<(std::ostream &in, const T &item) {
              ^
/Users/henryiii/git/fitting/goofit/extern/rang/include/rang.hpp:471:42: note: candidate function [with T = rang::style]
inline rang_implementation::enableStd<T> operator<<(std::ostream &os,
```

It seems like CLI11 is colliding with rang's overloaded operator. Edit: It's the exported enum operator; I think this is safe (it's just being picked up if I export the CLI namespace, which is not a problem normally).

(Slowly working on this)



Patch:
diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp
index fe8a04b75..023edd737 100644
--- a/include/CLI/TypeTools.hpp
+++ b/include/CLI/TypeTools.hpp
@@ -219,15 +219,24 @@ template <typename S> class is_tuple_like {
 };
 
 /// Convert an object to a string (directly forward if this can become a string)
-template <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>
+template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
 auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
     return std::forward<T>(value);
 }
 
+/// Construct a string from the object
+template <typename T,
+          enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
+                      detail::enabler> = detail::dummy>
+std::string to_string(const T &value) {
+    return std::string(value);
+}
+
 /// Convert an object to a string (streaming must be supported for that type)
 template <typename T,
-          enable_if_t<!std::is_constructible<std::string, T>::value && is_ostreamable<T>::value, detail::enabler> =
-              detail::dummy>
+          enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
+                          is_ostreamable<T>::value,
+                      detail::enabler> = detail::dummy>
 std::string to_string(T &&value) {
     std::stringstream stream;
     stream << value;


Test patch:
diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp
index d3aa83750..633072ee8 100644
--- a/tests/AppTest.cpp
+++ b/tests/AppTest.cpp
@@ -1885,6 +1885,27 @@ TEST_F(TApp, VectorUnlimString) {
     EXPECT_EQ(answer, strvec);
 }
 
+// From https://github.com/CLIUtils/CLI11/issues/420
+TEST_F(TApp, stringLikeTests) {
+    struct nType {
+        explicit nType(const std::string &a_value) : m_value{a_value} {}
+
+        explicit operator std::string() const { return std::string{"op str"}; }
+
+        std::string m_value;
+    };
+
+    nType m_type{"abc"};
+    app.add_option("--type", m_type, "type")->capture_default_str();
+    run();
+
+    EXPECT_EQ(app["--type"]->as<std::string>(), "op str");
+    args = {"--type", "bca"};
+    run();
+    EXPECT_EQ(std::string(m_type), "op str");
+    EXPECT_EQ(m_type.m_value, "bca");
+}
+
 TEST_F(TApp, VectorExpectedRange) {
     std::vector<std::string> strvec;