How can I deal with unwanted template creation for a function that is not called?

I am trying to implement some code where I want to call certain templated methods based on type traits. Unfortunately, methods are created using types (which are never called with the specified types) that are incompatible with the function, which causes compilation errors. Ideally, I could use traits to prevent unclaimed methods from being generated, but that is not the case, so I am looking for a workaround or design pattern that I can use.

I have some simple POD structures. Each type has different members (their existence is indicated by the corresponding signs)

struct ThingA
{
    int a;
};

struct ThingAB
{
    int a;
    int b;
};

struct ThingABC
{
    int a;
    int b;
    int c;
};

template <typename Thing>
struct ThingTraits
{
    static const bool has_a=false;
    static const bool has_b=false;
    static const bool has_c=false;
};

      

Here's the template function; ThingA, ThingAB and ThingABC can be passed to it as Src or Dst type

template<typename Src, typename Dst>
void assign(Src const &src, Dst &dst)
{
    constexpr bool c_a = ThingTraits<Src>::has_a && ThingTraits<Dst>::has_a;
    constexpr bool c_b = ThingTraits<Src>::has_b && ThingTraits<Dst>::has_b;
    constexpr bool c_c = ThingTraits<Src>::has_c && ThingTraits<Dst>::has_c;

    c_a ? copy_a(src,dst) : do_nothing();
    c_b ? copy_b(src,dst) : do_nothing();
    c_c ? copy_c(src,dst) : do_nothing();
}

      

The copy_a / b / c methods simply copy the corresponding element:

template<typename Src, typename Dst>
void copy_a(Src const &src, Dst &dst)
{
    dst.a = src.a;
}

template<typename Src, typename Dst>
void copy_b(Src const &src, Dst &dst)
{
    dst.b = src.b;
}

template<typename Src, typename Dst>
void copy_c(Src const &src, Dst &dst)
{
    dst.c = src.c;
}

      

When trying to compile this, I get errors because copy_a / b / c is created for types where the required members do not exist (i.e. copy_b).

How can I implement something functionally equivalent since this way doesn't really work? I need ThingA, AB, ABC to remain simple PODs with no extra members (req size is strict), and I don't want to define which copy operations to invoke at runtime.

+3


source to share


3 answers


You might have something like:

template<bool hasA>
struct copy_a_caller
{
    template <typename Src, typename Dst>
    void operator () (const Src& src, Dst& dst) const
    {
        dst.a = src.a;
    }
};

template<>
struct copy_a_caller<false>
{
    template <typename Src, typename Dst>
    void operator () (const Src&, Dst&) const {}
};

template<typename Src, typename Dst>
void copy_a(Src const &src, Dst &dst)
{
    copy_a_caller<ThingTraits<Src>::has_a && ThingTraits<Dst>::has_a>()(src, dst);
}

// similar thing for b and c

      



And then

template<typename Src, typename Dst>
void assign(Src const &src, Dst &dst)
{
    copy_a(src, dst);
    copy_b(src, dst);
    copy_c(src, dst);
}

      

+4


source


You can just use the delegate-class trick and (partial) specialization:

template<typename Src, typename Dst>
void assign(Src const &src, Dst &dst)
{
    constexpr bool c_a = ThingTraits<Src>::has_a && ThingTraits<Dst>::has_a;
    constexpr bool c_b = ThingTraits<Src>::has_b && ThingTraits<Dst>::has_b;
    constexpr bool c_c = ThingTraits<Src>::has_c && ThingTraits<Dst>::has_c;

    copy_a<c_a>::call(src, dst);
    copy_b<c_b>::call(src, dst);
    copy_c<c_c>::call(src, dst);
}

template <bool HasA>
struct copy_a;

template <>
struct copy_a<true>
{
  template <class Src, class Dst>
  static void call(Src const &src, Dst &dst)
  { src.a = dst.a; }
};

template <>
struct copy_a<false>
{
  template <class Src, class Dst>
  static void call(Src const &, Dst &)
  {}
};

      



copy_b

and copy_c

remain as an exercise for the reader :-)

+3


source


Although slightly more than the other answers, here's an example of using SFINAE , specifically the "has some member" trick to accomplish what you are trying to do:

// Using this as a "type expression container" to force
// the deduction of a type. If said deduction fails,
// the compiler will defer emitting an error message
// until it fails to bind any of the functions below.
//
template<typename nope_t>
struct voidme{using type = void;};

// Using a late return type here allows us to
// reference argument symbols.
//
// Note that the expression within the decltype()
// does not require knowledge of any concrete type.
//
template<typename source_t, typename target_t>
auto copy_a(const source_t &src, target_t &dst)
-> typename voidme<decltype(dst.a = src.a)>::type
{
    dst.a = src.a;
}

// A "catch-all" copy_a. If type deduction fails in
// the above version, the compiler will attempt to
// bind this version, which will succeed.
//
// This takes advantage of the "Not An Error" part of SFINAE.
//
void copy_a(...)
{
}

template<typename source_t, typename target_t>
auto copy_b(const source_t &src, target_t &dst)
-> typename voidme<decltype(dst.b = src.b)>::type
{
    dst.b = src.b;
}

void copy_b(...)
{
}

template<typename source_t, typename target_t>
auto copy_c(const source_t &src, target_t &dst)
-> typename voidme<decltype(dst.c = src.c)>::type
{
    dst.c = src.c;
}

void copy_c(...)
{
}


template<typename source_t, typename target_t>
void assign(const source_t &src, target_t &dst)
{
    copy_a(src, dst);
    copy_b(src, dst);
    copy_c(src, dst);
}

      

In the other place...

struct ThingWithA
{
    int a;
};

struct ThingWithB
{
    int b;
};

struct ThingWithAandB
{
    int a;
    int b;
};



void DoTheThings()
{
    ThingWithA srcA, dstA;

    ThingWithB srcB, dstB;

    ThingWithAandB srcAB, dstAB;

    // assign() wherein "copy_b" and "copy_c" do nothing.
    assign(srcA, dstA);

    // assign() wherein "copy_a" and "copy_c" do nothing.
    assign(srcB, dstB);

    // assign() wherein only "copy_c" does nothing.
    assign(srcAB, dstAB);
}

      

Note that this approach works for any type and does not require the use of traits.

If there are no other constraints to enforce for the types involved, then it is my opinion that the parameterized traits you described are maintenance hazards (they should be specialized for each type that should be used with assign

, revealing their definitions for this detail) ...

+2


source







All Articles