pfultz2 / Tick

Trait introspection and concept creator for C++11
http://tickcpp.readthedocs.io/en/latest/doc/
Boost Software License 1.0
183 stars 18 forks source link

`require` with non-template arguments breaks #14

Closed akrzemi1 closed 7 years ago

akrzemi1 commented 7 years ago

The following program works fine (on GCC 7.2):

#include <tick/builder.h>

struct has_f2_r : tick::ops
{
  template<class T>
  auto require(T&& x) -> valid<
    decltype(x.f(1, 2)) // <- using literals 1 and 2
  >;
};

template<class T>
struct has_f2 : tick::models<has_f2_r, T>
{};

struct A { void f(int, int) {} };
struct B { void f(int) {} };

int main()
{
    static_assert(!has_f2<B>::value, "** 1");
    static_assert(has_f2<A>::value, "** 2");
}

But I do not like it, because I am using literals 1 and 2 rather than saying "any i, j of type int. So I change function require to:

  template<class T>
  auto require(T&& x, int i, int j) -> valid<
    decltype(x.f(i, j))
  >;

And now my concept stops working: it will always return false. The entire program:

#include <tick/builder.h>

struct has_f2_r : tick::ops
{
  template<class T>
  auto require(T&& x, int i, int j) -> valid<
    decltype(x.f(i, j))
  >;
};

template<class T>
struct has_f2 : tick::models<has_f2_r, T>
{};

struct A { void f(int, int) {} };
struct B { void f(int) {} };

int main()
{
    static_assert(!has_f2<B>::value, "** 1");
    static_assert(has_f2<A>::value, "** 2"); // <- fires!
}

However, when I provide my own simplified implementation of models based on yours, it works again:

#include <type_traits>
#include <tick/builder.h>

struct has_f2_r : tick::ops
{
  template<class T>
  auto require(T&& x, int i, int j) -> valid<
    decltype(x.f(i, j))
  >;
};

template <typename... Ts>
using void_t = void;

template <typename _, typename C, typename... Args>
struct models_ : std::false_type {};

template <typename C, typename... Args>
struct models_<void_t<decltype(&C::template require<Args...>)>, C, Args...>
  : std::true_type {};

template <typename C, typename... Args>
  using models = models_<void, C, Args...>;

template<class T>
struct has_f2
: models<has_f2_r, T>
{};

struct A { void f(int, int) {} };
struct B { void f(int) {} };

int main()
{
  static_assert(!has_f2<B>::value, "** 1");
  static_assert(has_f2<A>::value, "** 2");
}

So, I wonder, if this can be fixed in Tick?

pfultz2 commented 7 years ago

This changes the semantics of how require works. By using a MF pointer, overloading require no longer works, which is used for some traits in the library.

Also, tick uses type deduction to fill in the template types which seems more natural(at least to me). So if you write a trait like:

struct has_foo_r : tick::ops
{
    template<class T>
    auto require(const T& x) -> valid<
        decltype(x.foo())
    >;
};

It will work perfectly fine for has_foo<T>{} and has_foo<const T>{}. Using a MF pointer has_foo<const T>{} will work, but has_foo<T>{} will not, although I believe it should because it still meets the type requirements even if the const is missing.

But for your case alternatively, you could use declval:

struct has_f2_r : tick::ops
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(x.f(std::declval<int>(), std::declval<int>()))
    >;
};

Also, using default parameters should work as well:

struct has_f2_r : tick::ops
{
    template<class T>
    auto require(T&& x, int i = 0, int j = 0) -> valid<
        decltype(x.f(i, j))
    >;
};
akrzemi1 commented 7 years ago

Ok, thanks for the explanation. My primary motivation for testing Tick is to avoid using declval everywhere. So option #1 will not work for me. Option #2 looks promising, but I do not know how it is going to scale to non-POD types.