Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
EnzoMafo
Jul 29, 2008
Pretty dumb question. Is there less ugly/confusing than the below way to forward declare a class that is defined in a deeply nested namespace? For example, boost ASIO sockets:

code:
namespace boost
{
  namespace asio
  {
    namespace ip
    {
      namespace udp
      {
        class socket;
      }
    } 
  }
}

Adbot
ADBOT LOVES YOU

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
Well you can do dumb things with macros that aren't at all worth it:
code:
#define FORWARD1(ns, cls) namespace ns { cls; }
#define FORWARD2(ns1, ns2, cls) namespace ns1 { FORWARD1(ns2, cls) }
#define FORWARD3(ns1, ns2, ns3, cls) namespace ns1 { FORWARD2(ns2, ns3, cls) }
#define FORWARD4(ns1, ns2, ns3, ns4, cls) namespace ns1 { FORWARD3(ns2, ns3, ns4, cls) }

FORWARD4(boost, asio, ip, upd, class socket)
Or you can just hold off on pushing the enter key, because using 13 lines to write something that's only 88 characters with whitespace is kinda dumb.
code:
namespace boost { namespace asio { namespace ip { namespace udp { class socket; } } } }

That Turkey Story
Mar 30, 2003

EnzoMafo posted:

Pretty dumb question. Is there less ugly/confusing than the below way to forward declare a class that is defined in a deeply nested namespace? For example, boost ASIO sockets:

code:
namespace boost
{
  namespace asio
  {
    namespace ip
    {
      namespace udp
      {
        class socket;
      }
    } 
  }
}

No, but if you find yourself doing stuff like this a lot, you can make a macro for it:

code:
#include <boost/preprocessor/arithmetic/dec.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/pop_back.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>

// Use: NESTED_DECL( boost, asio, ip, udp, class socket; )
// Yields:
//   namespace boost { namespace asio { namespace ip { namespace udp {
//     class socket;
//   } } } }
#define NESTED_DECL( ... )                                                     \
NESTED_DECL_IMPL_SEQ( BOOST_PP_VARIADIC_TO_SEQ( __VA_ARGS__ ) )

#define NESTED_DECL_IMPL_SEQ( seq )                                            \
BOOST_PP_SEQ_FOR_EACH( NESTED_DECL_HAS_NAMESPACE_MACRO, ~                      \
                     , BOOST_PP_SEQ_POP_BACK( seq )                            \
                     )                                                         \
BOOST_PP_SEQ_ELEM( BOOST_PP_DEC( BOOST_PP_SEQ_SIZE( seq ) ), seq )             \
BOOST_PP_REPEAT_FROM_TO                                                        \
( 1, BOOST_PP_SEQ_SIZE( seq ), NESTED_DECL_END_NAMESPACE_MACRO, ~ )

// Macros used during iteration to open and close the namespace
#define NESTED_DECL_HAS_NAMESPACE_MACRO( r, data, elem ) namespace elem {
#define NESTED_DECL_END_NAMESPACE_MACRO( r, data, elem ) }
Edit: Sort of beaten. Variadics win :|

That Turkey Story fucked around with this message at 00:52 on Jan 31, 2012

raminasi
Jan 25, 2005

a last drink with no ice
code:
class bar;

class expensive_to_calculate { 
public:
    expensive_to_calculate(bar *);
}

class not_default_constructable {
public:
    not_default_constructable(const expensive_to_calculate &);
};

class something_else {
public:
    something_else(const expensive_to_calculate &)
};

class foo {
private:
    not_default_constructable ndc;
    something_else se;
public:
    foo(bar * b) { // whoops! no default constructors for members!
        expensive_to_calculate etc(b);
        ndc = not_default_constructable(etc);
        se = something_else(etc);
    }
};
What are my choices? I'm seeing "use boost::optional or something like that," "give not_default_constructable a default constructor and allow it to exist in an uninitialized state" , and "don't expose this public foo constructor." In this particular case they're all possible but a little messy.

Paniolo
Oct 9, 2007

Heads will roll.
A pointer?

raminasi
Jan 25, 2005

a last drink with no ice
So that's basically option (a), then.

Paniolo
Oct 9, 2007

Heads will roll.
Well if your use case is exactly the same as the psudocode you provided, you could do something like this:

code:
class holds_expensive_to_construct
{
public:
    holds_expensive_to_construct(bar * b) 
    : etc(b)
    {
    }

    expensive_to_construct etc;
};

class foo
{
   // ...
   foo(const holds_expensive_to_construct& holder)
   : ndc(holder.etc), se(holder.etc)
   {
   }
};

int main()
{
   bar* b;
   foo f(b);
}
That'll only work in limited contexts though.

Do any C++ compilers support delegating constructors yet? That would let you implement something like this while keeping the implementation details entirely hidden within the class, i.e.:

code:
class foo
{
   // ...
   foo(bar* b) : foo(holds_expensive_to_construct(b)) { }
private:
   class holds_expensive_to_construct
   {
      // ...
   };
   foo(const holds_expensive_to_construct&) // etc.
};

Paniolo fucked around with this message at 05:58 on Jan 31, 2012

thermal
Feb 24, 2008

That Turkey Story posted:

No, but if you find yourself doing stuff like this a lot, you can make a macro for it:

Yeah, but your scientists were so preoccupied with whether they could that they didn't stop to think if they should.

That Turkey Story
Mar 30, 2003

thermal posted:

Yeah, but your scientists were so preoccupied with whether they could that they didn't stop to think if they should.

:rolleyes: I don't use the preprocessor so it's evil!!!

thermal
Feb 24, 2008

That Turkey Story posted:

:rolleyes: I don't use the preprocessor so it's evil!!!

not trying to be a luddite, just pointing out that nothing is gained by adding that code, and instead readability is lost. everyone knows what nested declarations look like; not everyone knows how the boost preprocessor library works. there is no value provided here, just obfuscation

MarsMattel
May 25, 2001

God, I've heard about those cults Ted. People dressing up in black and saying Our Lord's going to come back and save us all.
Wait, so TTS was serious?

That Turkey Story
Mar 30, 2003

I'm always serious about C++.

Princess Kakorin
Nov 4, 2010

A real Japanese Princess
So, what am I doing wrong with this template function?

code:
class conversion
{
   template<typename T>
    static T convert_string(const std::string &str)
    {
        //Create a stringstream object containing the string passed through
        std::istringstream toConvert(str);
        //Create the return type
        T returnType;


        std::cout << "Attempting to convert "+(std::string)toConvert+" to "+(std::string)typeid(returnType).name() << std::endl;
        //Attempt to convert the string to the returntype
        if(!(toConvert >> returnType))
        {
           exitWithError("Could not convert to "+(std::string)typeid(returnType).name());
        }

        //If conversion was successful, return the converted string
        return returnType;

    }
};
I keep getting the error,
error: no matching function for call to 'conversion::convert_string(std::string&)'

Whenever I try to run conversion::convert_string(somestring);

chglcu
May 17, 2007

I'm so bored with the USA.
You need to tell it what T is in this case, I believe.
code:
Foo foo = conversion::convert_string<Foo>(somestring);
The compiler isn't able to infer the type of T unless its one of the function parameters.

Edit: By "isn't able to", I mean "doesn't". Never was quite sure what the reasoning for not doing it was though. Never bothered to look up the why in this case.

chglcu fucked around with this message at 04:10 on Feb 2, 2012

That Turkey Story
Mar 30, 2003

Princess Kakorin posted:

So, what am I doing wrong with this template function?

prolecat pointed out the problem. I don't know of the rationale for why, if there is one at all. If you really want the syntax you are looking for, you can get it by making convert_string not a template and having its definition just return an instance of a different type that holds the string argument by pointer or reference. That other type should have an overloaded conversion operator template that does what your current function template does.

Paniolo
Oct 9, 2007

Heads will roll.
How could the compiler possibly know what T is supposed to be if you don't explicitly specify it and it's not a parameter?

tractor fanatic
Sep 9, 2005

Pillbug

Paniolo posted:

How could the compiler possibly know what T is supposed to be if you don't explicitly specify it and it's not a parameter?

It can infer it from the type you are assigning to, in the same way it can use a templated conversion operator.

That Turkey Story
Mar 30, 2003

Paniolo posted:

How could the compiler possibly know what T is supposed to be if you don't explicitly specify it and it's not a parameter?

It can deduce it just like an implicit conversion is deduced as long as the result is immediately used in a place that a specific type is expected.

Princess Kakorin
Nov 4, 2010

A real Japanese Princess
Thanks. It is pretty dumb it can't tell what type it needs to return. But whatever. Got it all working great now!

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
It's easy to look at specific examples and think "the compiler should be able to infer that." The question is, how general do you want the facility to be, and what are you willing to sacrifice to make it possible?

In this case, you have a call to a single function template whose template argument is used only in the result type, and it appears in a context which unambiguously states what that type should be. Is this the only case where this inference works?
  • What if I need an explicit conversion (constructor or conversion function) to get to the context type?
  • What if I use the result, but it's not the initialization of a variable? What if I just assign it to a variable instead, is that unambiguous enough? What if I do a member access? A qualified member access? What if it's decltype or something?
  • Even more interestingly, what if I use the result as a call argument? What if it's an overloaded call? What if it's a call to a function template? What if it's a call to a function template whose result might be contextually inferable? What if it's a call that might use argument-dependent lookup?
  • Which of the previous answers change, if any, if one of the template arguments used in the result type (and therefore potentially inferable) also appears elsewhere in the signature of the function template? How does this interact with ordinary template argument deduction? How much do I have to resolve about the context in order to judge this?
  • What if the call itself is overloaded? How does this "type context" interact with overload resolution? What are the rules for inferring a type from context? The existing template argument deduction rules mostly deal with the opposite direction of inference.

I think the problem is mostly that it's really easy to come up with a rule that types exactly this case and falls over into exponential time or even undecidability with the slightest bit of extension. It's also a lot easier to understand overload resolution when you can actually extract out a specific bit of syntax and analyze it independently. The current rules are really damned complicated, but they actually work and extend reasonably well, and to do so they rely very heavily on being able to unambiguously assign a type to an expression without considering the context.

Paniolo
Oct 9, 2007

Heads will roll.

That Turkey Story posted:

It can deduce it just like an implicit conversion is deduced as long as the result is immediately used in a place that a specific type is expected.

That's something very different, though.

When a compiler sees an assignment operation it needs to know the type of the expression on the right-hand side so it can determine which overload of operator= (or a constructor) to invoke.

When you're talking about implicit casts, the type of the right-hand side is already known, and you're just determining what intermediate operation is needed to transform that type into something which can be accepted by an existing overload of operator= (or a constructor.)

Being able to do implicitly determine the type of an expression based on the context of an assignment higher up in the parse tree, even if you decided you would error out in any case that was slightly ambiguous, would require parsing C++ to be even more complicated than it already is, which is already too complicated.

That Turkey Story
Mar 30, 2003

Paniolo posted:

That's something very different, though.

When a compiler sees an assignment operation it needs to know the type of the expression on the right-hand side so it can determine which overload of operator= (or a constructor) to invoke.

When you're talking about implicit casts, the type of the right-hand side is already known, and you're just determining what intermediate operation is needed to transform that type into something which can be accepted by an existing overload of operator= (or a constructor.)

It's actually not different at all. Just replace "object with implicit conversion" with "object of unnamed type that has implicit conversion". The very fact that you can so easily emulate it in standard C++ in the manner I already described is proof of that. It does't have to do anything more complicated than that, which is why it works. If it were in the standard, I imagine it would even be described as such in terms of implicit conversion of an unnamed type.

Paniolo posted:

Being able to do implicitly determine the type of an expression based on the context of an assignment higher up in the parse tree, even if you decided you would error out in any case that was slightly ambiguous, would require parsing C++ to be even more complicated than it already is, which is already too complicated.

Hmm? No changes to parsing should have to be done at all, this isn't a parsing problem. In terms of how the type is determined, just use the same rules as are used with implicit conversions. You don't have to look at the implementation of the function template or anything, just do the exact same matching as that of the return type of an implicit conversion. If after the deduction is done the corresponding function template has an error during instantiation then you get just that, an error during instantiation of the function template.

Since you seem to be missing what I'm saying, here is exactly what I described in my earlier reply, trying to change as little from the earlier example as possible:

code:
class conversion
{
  struct string_converter
  {
    explicit string_converter( std::string const& str ) : m_str( str ) {}

    template< class T >
    operator T() const
    {
      //Create a stringstream object containing the string passed through
      std::istringstream toConvert(m_str);
      //Create the return type
      T returnType;


      //std::cout << "Attempting to convert "+toConvert.str()+" to "+(std::string)typeid(returnType).name() << std::endl;
      //Attempt to convert the string to the returntype
      if(!(toConvert >> returnType))
      {
        exitWithError("Could not convert to "+(std::string)typeid(returnType).name());
      }

      //If conversion was successful, return the converted string
      return returnType;
    }
  private:
    std::string const& m_str;
  };

public:
  static string_converter convert_string(const std::string &str)
  {
    return string_converter( str );
  }
};
This works fine and does what the poster wants. If the standard were to do this automatically, they would probably even word it in terms of the return type of an implicit conversion since that behavior is sensible here and doesn't require introducing more complicated rules.

rjmccall posted:

It's easy to look at specific examples and think "the compiler should be able to infer that." The question is, how general do you want the facility to be, and what are you willing to sacrifice to make it possible?

In this case, you have a call to a single function template whose template argument is used only in the result type, and it appears in a context which unambiguously states what that type should be. Is this the only case where this inference works?

[snip list]

I think the problem is mostly that it's really easy to come up with a rule that types exactly this case and falls over into exponential time or even undecidability with the slightest bit of extension. It's also a lot easier to understand overload resolution when you can actually extract out a specific bit of syntax and analyze it independently. The current rules are really damned complicated, but they actually work and extend reasonably well, and to do so they rely very heavily on being able to unambiguously assign a type to an expression without considering the context.

Again, I don't see how this would make things any more complicated than the emulated version which already works in standard C++. If the coder wants that behavior he can get it with a little bit of boilerplate, so why not just let them do it directly?

Also, the emulated version works in more cases than a specific type being needed -- specifically, it can do all of the matching that happens with the return type of any implicit conversion (such as a type with an implicit conversion to std::pair< T1, T2 >, where at least one of T1 and T2 are template parameters).

That Turkey Story fucked around with this message at 10:13 on Feb 2, 2012

The1ManMoshPit
Apr 17, 2005

It's not impossible or anything; Ada allows you to overload functions by return type. It might be useful sometimes but given how many opportunities there already are to shoot yourself in the foot with unexpected implicit conversions in c++ it's probably better not to have it.

That Turkey Story
Mar 30, 2003

The1ManMoshPit posted:

It's not impossible or anything; Ada allows you to overload functions by return type. It might be useful sometimes but given how many opportunities there already are to shoot yourself in the foot with unexpected implicit conversions in c++ it's probably better not to have it.

I don't know, I think that's sort of a mistake in reasoning. We're emulating the functionality here via implicit conversions, since you want some similar behavior in terms of deduction, but it's not really an implicit conversion. For one, it's not implicit, at least not in the way that an implicit conversion is implicit -- you're making an explicit call to get the functionality, so it's not like you can accidentally run into the conversion when you don't want it. Second, you only use the result when it's deduced, otherwise you get an error (and you get an error if it's ambiguous), so it's not like you have an expression of a desired type and it's unexpectedly being converted to something else. You essentially have nothing at all before the deduction takes place.

Think of the functionality more like the automatic deduction of template arguments, which is something that we take for granted already. When you call std::for_each, you probably don't think it's unsafe that the template arguments are deduced, right? In other words, you (thankfully) don't have to do:

std::for_each< typename std::vector< T >::const_iterator, some_function_object >( my_T_vector.begin(), my_T_vector.end(), some_function_object() );

Instead, you just pass in the objects, leaving the template arguments to be deduced automatically by the compiler. If we lived in a world where you had to call function templates like the example I posted above, then it would be an error to not specify them when making a call. In the for_each example, what have we lost by automatically deducing the template arguments? In terms of functionality here, nothing. Arguably, someone could say that which instantiation is used may be unclear, though if that really were a problem then there's nothing stopping a programmer from being explicit even though they don't have to. In practice, we generally don't do that because the behavior is already pretty clear.

A similar thing happens with automatic return type deduction. What happens currently in C++ when you try to do what the original poster was doing? You get an error. If we make it work, are we losing anything? Not really. Again, you can argue, just like with the template argument deduction example, that you are losing clarity, but if you really feel that that is the case, then there's nothing stopping you from being explicit if you want to, you just don't have to be explicit.

And keep in mind that I'm not saying that the return type pattern should be considered when doing resolution for the function template being called. Even in that case, though, I'm not entirely convinced that it would be too horrible, only because I don't imagine that situations where it would be complicated to deduce would be common in practice.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

That Turkey Story posted:

Again, I don't see how this would make things any more complicated than the emulated version which already works in standard C++. If the coder wants that behavior he can get it with a little bit of boilerplate, so why not just let them do it directly?

Note that your emulation forces it down into exactly what I described as the easy case: the result type completely does not affect the choice of overload and the template arguments used in the "templated result type" are necessarily used only there. It also misbehaves in a ton of cases: argument-dependent lookup, template argument deduction, sizeof, decltype, etc. Basically, the only contexts in which it works are (1) calls/operators not involving templates and (2) initializations.

The fact that the emulation does not affect overload resolution is very important; C++'s overload design relies on defining away function templates by applying template argument deduction as a separate phase, then (almost) completely ignoring whether a particular candidate is a template specialization. Trying to apply that faithfully here means that we have to simultaneously check an entire cluster of function calls and operators. Again, that's going to do evil things to ADL, which for operators really does matter; it's also going to foul up template argument deduction if any of the downstream calls are templated. Plus, as a minor matter, it's obviously combinatorial in time and space. If you *change* the rule, and maybe do overload resolution purely on call arguments against the inferred function signature, simply allowing template arguments used only in the result type to go unspecified for now, then we get back to something more workable, but we've also foreclosed a lot of potentially-interesting functionality.

Ada does overloading by result type but (IIRC) does not have an equivalent of template argument deduction.

rjmccall fucked around with this message at 19:48 on Feb 2, 2012

That Turkey Story
Mar 30, 2003

rjmccall posted:

Note that your emulation forces it down into exactly what I described as the easy case: the result type completely does not affect the choice of overload and the template arguments used in the "templated result type" are necessarily used only there. It also misbehaves in a ton of cases: argument-dependent lookup, template argument deduction, sizeof, decltype, etc. Basically, the only contexts in which it works are (1) calls/operators not involving templates and (2) initializations.

I wouldn't call that misbehaving, I'd just call that a necessary limitation. Not being able to do sizeof( convert_from_string( "1234" ) ) is akin to trying to take the address of an overloaded function without explicitly casting for disambiguation. Just as the answer to taking the address of the overloaded function is "be explicit", so is the answer to wanting to do sizeof or decltype of the result of one of our function templates whose return type is to be deduced. Doing sizeof or decltype doesn't work because it's ambiguous.

rjmccall posted:

The fact that the emulation does not affect overload resolution is very important; C++'s overload design relies on defining away function templates by applying template argument deduction as a separate phase, then (almost) completely ignoring whether a particular candidate is a template specialization. Trying to apply that faithfully here means that we have to simultaneously check an entire cluster of function calls and operators. Again, that's going to do evil things to ADL, which for operators really does matter; it's also going to foul up template argument deduction if any of the downstream calls are templated. Plus, as a minor matter, it's obviously combinatorial in time and space.

Again, I'm not advocating any of this. The emulated version is good enough for basically all cases and I wouldn't expect the language feature to go beyond that. A return type affecting overload resolution of the function template of which it is the return type is not at all what I originally suggested (that was a mouthful), nor is it what the original poster was looking for, anyway.


rjmccall posted:

Ada does overloading by result type but (IIRC) does not have an equivalent of template argument deduction.

Right, I seem to recall reading about that in an interview with Stepanov where one of either he or Stroustrup wanted template argument deduction and the other was against it (or maybe it was in Elements of Programming). Either way, I'm grateful that we got it in C++.

The1ManMoshPit
Apr 17, 2005

That Turkey Story posted:

I don't know, I think that's sort of a mistake in reasoning. We're emulating the functionality here via implicit conversions, since you want some similar behavior in terms of deduction, but it's not really an implicit conversion. For one, it's not implicit, at least not in the way that an implicit conversion is implicit -- you're making an explicit call to get the functionality, so it's not like you can accidentally run into the conversion when you don't want it. Second, you only use the result when it's deduced, otherwise you get an error (and you get an error if it's ambiguous), so it's not like you have an expression of a desired type and it's unexpectedly being converted to something else. You essentially have nothing at all before the deduction takes place.

I'm just not a big fan of implicit conversion in general, IMO the keyword "explicit" shouldn't even exist in c++, "implicit" should instead. The problem with allowing multiple return types is it becomes much harder to reason about which version of foo() is getting called when you assign to a type that requires an implicit conversion, much like it's already hard to reason about what overloaded function gets called. It becomes way harder when you have code like foo(bar()) where both foo & bar have multiple overloads.

I don't think it's impossible and I have found myself thinking "gee it would be useful to be able to overload by return type here" before, but when I think about the implications due to c++'s already sometimes hard to understand function call resolution rules it makes me wary.

quote:

Think of the functionality more like the automatic deduction of template arguments, which is something that we take for granted already. When you call std::for_each, you probably don't think it's unsafe that the template arguments are deduced, right? In other words, you (thankfully) don't have to do:

std::for_each< typename std::vector< T >::const_iterator, some_function_object >( my_T_vector.begin(), my_T_vector.end(), some_function_object() );

Instead, you just pass in the objects, leaving the template arguments to be deduced automatically by the compiler. If we lived in a world where you had to call function templates like the example I posted above, then it would be an error to not specify them when making a call. In the for_each example, what have we lost by automatically deducing the template arguments? In terms of functionality here, nothing. Arguably, someone could say that which instantiation is used may be unclear, though if that really were a problem then there's nothing stopping a programmer from being explicit even though they don't have to. In practice, we generally don't do that because the behavior is already pretty clear.

That kind of stuff can be cool, but here's a favourite counter-example of mine: which constructor gets called for i & which for j, and how many lines of code in the STL is required to make both of these calls do the same thing?

code:
        std::vector<unsigned int> i(1, 2);
        std::vector<unsigned int> j(1u, 2u);
The fact that the STL is designed to hide the unexpected behaviour here is admirable, but it can place an unnecessary burden on developers wanting to create similarly robust code. There are obvious benefits when you finally have a working library, and if it's already made for you all the better, but there are a lot of crazy cases to account for if you want your code to be easy to call into. I imagine allowing overload by return type would just increase the number of workarounds of this type you would need to do.

quote:

A similar thing happens with automatic return type deduction. What happens currently in C++ when you try to do what the original poster was doing? You get an error. If we make it work, are we losing anything? Not really. Again, you can argue, just like with the template argument deduction example, that you are losing clarity, but if you really feel that that is the case, then there's nothing stopping you from being explicit if you want to, you just don't have to be explicit.

And keep in mind that I'm not saying that the return type pattern should be considered when doing resolution for the function template being called. Even in that case, though, I'm not entirely convinced that it would be too horrible, only because I don't imagine that situations where it would be complicated to deduce would be common in practice.

Like I said, I think for sure there are places where it would be useful, but it's hard to try and wrap my brain around the number of crazy cases that would inevitably arise.

Jethro
Jun 1, 2000

I was raised on the dairy, Bitch!

The1ManMoshPit posted:

IMO the keyword "explicit" shouldn't even exist in c++, "implicit" should instead.
If you have to type it, it's not implicit :v:.

That Turkey Story
Mar 30, 2003

The1ManMoshPit posted:

I'm just not a big fan of implicit conversion in general, IMO the keyword "explicit" shouldn't even exist in c++, "implicit" should instead.

You'll get no argument from me there since I feel the same way. I'm also somewhat out of the ordinary in that I don't like the way automatically pulled-in default special member functions work. At the very least, I think automatic pulling-in of defaults should be inhibited in regard to the rule of 3 (I.E. if copy or assign or destruction are defined, inhibit the automatic pulling-in of defaults for the others). I'd much rather that you had to explicitly pull them in with something like C++11's = default syntax when you want them (or something less verbose).

Anyway, I don't think the situation we are talking about here is entirely analogous to any of those things mentioned. I suppose if you really wanted to avoid that behavior, you could make it opt-in or opt-out by writing "implicit" or "explicit" when defining the template. Either way, I don't see anything wrong with the functionality itself.

The1ManMoshPit posted:

The problem with allowing multiple return types is it becomes much harder to reason about which version of foo() is getting called when you assign to a type that requires an implicit conversion, much like it's already hard to reason about what overloaded function gets called. It becomes way harder when you have code like foo(bar()) where both foo & bar have multiple overloads.
First, again, people keep trying to turn this into a slippery-slope argument. As I stated a couple of times already, I do not advocate using the return-type deduction as a consideration during overload resolution in regard to the function being called aside from simply determining which instantiation of the already-chosen template should be used.

In other words, if one of the bar overloads is a non-template, it will be preferred over the template in all cases. If there are just multiple bar overloads that are all templates and there are multiple matches that would be ambiguous without any regard to the return type, then the call is ambiguous and you get a compile error that could be resolved by being explicit. The fact that foo is overloaded should only affect things if bar() resolves to a function template with an automatically deduced return type and the overloads of foo have two different parameter types (or if it's dependent on a template parameter, etc.), in which case you get a compile error that could be resolved by being explicit.

Second, your example is contrived. It's easy to come up with seemingly confusing situations when you use arbitrary functions that would likely not be designed in practice, especially when they are simply named foo and bar, giving us no information about what we would or should expect. So, taking your example, what are the functions foo and bar supposed to be doing here; why do these overloads exist/why were they designed in this manner; what would the person doing foo(bar()) reasonably expect to happen; does what he expects actually differ from what the compiler would do; if there is a difference, does the compiler produce an error, specifically one that could be fixed by being explicit, or does it compile fine but produce unexpected results at run-time? If you want to have a little more sway with me here, show me an example that could reasonably come up in code rather than one with just foo and bar. I could probably make any language feature look overly confusing with contrived code.

Anyway, the main use-case is stuff like the situation that sparked this conversation. In practice, I can't imagine use-cases where you get horribly complicated situations. If you do, then IMO that's your mistake for using the feature in a confusing manner. If you have some code that helps your argument, I'd like to hear it, but right now I feel like what's generally being said is "it would be too confusing when a programmer uses it to write... confusing code".

The1ManMoshPit posted:

That kind of stuff can be cool, but here's a favourite counter-example of mine: which constructor gets called for i & which for j, and how many lines of code in the STL is required to make both of these calls do the same thing?

code:
        std::vector<unsigned int> i(1, 2);
        std::vector<unsigned int> j(1u, 2u);

I don't see how this really relates to what we are talking about. Anyway, if you want my personal opinion on the problem there, it's a combination of not having concepts, and IMO the interface could have been better designed.

The1ManMoshPit posted:

The fact that the STL is designed to hide the unexpected behaviour here is admirable, but it can place an unnecessary burden on developers wanting to create similarly robust code. There are obvious benefits when you finally have a working library, and if it's already made for you all the better, but there are a lot of crazy cases to account for if you want your code to be easy to call into. I imagine allowing overload by return type would just increase the number of workarounds of this type you would need to do.
I don't think that's true, at least not with how I've specified them. Show me how automatically deduced return types makes this worse.

The1ManMoshPit
Apr 17, 2005

That Turkey Story posted:

I don't see how this really relates to what we are talking about. Anyway, if you want my personal opinion on the problem there, it's a combination of not having concepts, and IMO the interface could have been better designed.

It was just an example of unexpected behaviour with function overloading and the amount of effort some people have expended to make it work how a reasonable person would expect, rather than how strict typing dictates. I guess it's only related to overload by return type inasmuch as it deals with overloading in general.

quote:

I don't think that's true, at least not with how I've specified them. Show me how automatically deduced return types makes this worse.

I have to admit when I tried to think of a not-totally-contrived example showing how overload by return value would make the situation worse I was stumped. I think I'm just getting old and conservative - I guess it's already annoying when you have to deal with the corner-cases of overload resolution and the thought of adding any more rules to it just generates a knee-jerk reaction.

I mean, the number of contrived examples you can come up with is staggering, but as you say in practice no sane person would ever consider writing code that way. There would certainly be horrible examples of people writing bad code but that's really no different than what we have now.

MrMoo
Sep 14, 2000

How does one clean up a factory generated object via unique_ptr? The logic I am looking for is as follows:
code:
std::unique_ptr<rfa::logger::AppLoggerMonitor> monitor_;

struct unregister_deleter {
	template <class T> void operator()(T* ptr) {
		monitor_->unregisterLoggerClient (ptr);	
	};
};
std::unique_ptr<rfa::common::Handle, unregister_deleter> handle_;
Am I just nuts for using smart pointers here?

My Rhythmic Crotch
Jan 13, 2011

This thread is massive and I have not read much of it, so I apologize if this has already been covered, but I'm kinda stuck.

In my project I've got a list of both base and derived objects, like so:
code:
	std::list<GameObject *> objects;
	std::list<DerivedObject *> derivedObjects;
When a new object is created, I have been adding it to both lists:
code:
	objects.push_back(thing1);
	derivedObjects.push_back(thing1);
When the object's update function returns false, that means it's time to delete the object:
code:
	std::list<GameObject *>::iterator it;
	for(it = objects.begin(); it != objects.end(); )
	{
		// If update returns false, object should be deleted
		if((*it)->update(timeElapsed_ms) == false)
		{
			delete (*it);
			it = objects.erase(it);
		}
		else
			++it;
	}
If you've read this far, you probably know exactly what my problem is: the object has been erased from the list of base objects but not the list of derived objects, and so there will be an access violation the next time the list of derived objects is iterated through.

I'm not really sure what to do here. The problem is that I will keep adding different derived classes and maintaining separate lists is just not a real solution. At different points in the project I may want to step through all base objects, or in other places I may want to process a few derived classes only.

I just need some advice how to handle all these different kinds of objects and obviously prevent things like access violations in my crappy list setup that I have now. Thanks for your thoughts and ideas.

Suspicious Dish
Sep 24, 2011

2020 is the year of linux on the desktop, bro
Fun Shoe
The quick solution is to have a boolean variable "isDerived" that's on each object, and then filter non-derived objects out when you iterate through the entire list.

Having said that, you'll get a much answer if you're specific with your details. Why do you want to iterate through base objects and don't care about derived objects? It seems like you're tying implementation details to the class hierarchy, which may violate the Liskov substitution principle.

Gerblyn
Apr 4, 2007

"TO BATTLE!"
Fun Shoe
On a basic level, I'd do something like this:

code:
class MyListManager
{
public:
   std::list<Object*>     myObjects;
   std::list<Cheese*>     myCheeses;
};

class Object
{
public:
   virtual void AddToLists(MyListManager* listManager)
   {
      listManager->myObjects.push_back(this);
   }

   virtual void RemoveFromLists(MyListManager* listManager)
   {
      std::list<Object*>::iterator iter=std::find(listManager->myObjects.begin(), listManager->myObjects.end(),this);

      assert(iter!=listManager->myObjects.end());
      listManager->myObjects.erase(iter);
   }
};

class Cheese : public Object
{
public:
   virtual void AddToLists(MyListManager* listManager)
   {
      Object::AddToLists(listManager);

      listManager->myCheeses.push_back(this);
   }

   virtual void RemoveFromLists(MyListManager* listManager)
   {
      Object::RemoveFromLists(listManager);

      std::list<Cheese*>::iterator iter=std::find(listManager->myCheeses.begin(), listManager->myCheeses.end(),this);

      assert(iter!=listManager->myCheeses.end());
      listManager->myCheeses.erase(iter);
   }
}
So each object takes responsibility for adding/removing itself from any lists it should be a member of. You just need to remember to call AddToLists/RemoveFromLists when objects are created and destroyed.

The above is pretty inefficient, since each RemoveFromLists call triggers multiple linear searches through all your lists. If you can give each object a unique ID, you could use it as a key and store the objects in an std::map or std::hash_map instead, which would be much faster. You could also store the objects in std::set, using the pointer to the object itself as the key. The downside there would be the cost of adding new objects to the system would be higher, especially for map and set. Also, when you iterate through those containers, you won't get the objects in the order you inserted them, which I guess might be an issue for you.

Edit:

Suspicious Dish posted:

Having said that, you'll get a much answer if you're specific with your details. Why do you want to iterate through base objects and don't care about derived objects? It seems like you're tying implementation details to the class hierarchy, which may violate the Liskov substitution principle.

It's a fairly normal thing you may want to do in games. For example, you have your base entity type, and every frame you update all of them. However, some of those entities are bullets, which you want to do special collision checks with, so you keep a separate list of all the bullets to speed those checks up.

Gerblyn fucked around with this message at 13:46 on Feb 5, 2012

nielsm
Jun 1, 2009



Gerblyn posted:

You could also store the objects in std::set, using the pointer to the object itself as the key.
A std::set should be pretty fast for both adding, removing and iterating. The downside is that it is unordered. It also does not allow duplicates which may be desirable or undesirable.
If ordering is unimportant, use a set.

Gerblyn posted:

It's a fairly normal thing you may want to do in games. For example, you have your base entity type, and every frame you update all of them. However, some of those entities are bullets, which you want to do special collision checks with, so you keep a separate list of all the bullets to speed those checks up.

You could also have only lists of the various derived objects (and maybe one of "miscellaneous other objects") and then have a virtual view of them all in one collection. I.e. write an iterator that returns base class pointers and actually iterates through several separate collections of various derived types, in order.
(This has some fancy design pattern name that eludes me right now.)

Captain Cappy
Aug 7, 2008

nielsm posted:

A std::set should be pretty fast for both adding, removing and iterating. The downside is that it is unordered. It also does not allow duplicates which may be desirable or undesirable.
If ordering is unimportant, use a set.

Sets are ordered from least to greatest. There's also multi-set so that's not really a problem either.

nielsm
Jun 1, 2009



Captain Cappy posted:

Sets are ordered from least to greatest. There's also multi-set so that's not really a problem either.

Well you're right, although for pointers the less-than ordering is essentially arbitrary.

Dicky B
Mar 23, 2004

You can use a custom comparison class though.

My Rhythmic Crotch
Jan 13, 2011

Sigh...

After implementing what Gerblyn suggested, I am still getting spurious crashes in times of high activity (during times when there are lots of objects being created and destroyed).

Suspicious Dish did raise a good point. My derived classes have a lot of methods that the base class doesn't, and so I would have to rework my base class in order to correct that. I think I will rework the base class, and add a member function to the derived classes to return the class name. Then I can get away with a single list and still process objects only of a certain class when I want to.

It's cheesy but my C++ skills are being stretched a lot trying to deal with this... ugh.

Adbot
ADBOT LOVES YOU

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

My Rhythmic Crotch posted:

Suspicious Dish did raise a good point. My derived classes have a lot of methods that the base class doesn't, and so I would have to rework my base class in order to correct that. I think I will rework the base class, and add a member function to the derived classes to return the class name. Then I can get away with a single list and still process objects only of a certain class when I want to.
A thing I like to do, when I want to implement that behavior, is have the virtual function return a specific string that can also be accessed from elsewhere - this way you can use it as a string for debugging, but as a unique value for comparisons (so you don't have to do any string comparisons when identifying classes).

For example:
code:
class Base {
public:
  static const char *ID;
  virtual const char *ClassID() const {return ID;}
};

class SubClass1 : public Base {
public:
  static const char *ID;
  virtual const char *ClassID() const {return ID;}
};

const char *Base::ID="Base";
const char *SubClass1::ID="SubClass1";

int main() {
  SubClass1 object;
  printf("Class of object is %s\n",object.ClassID());
  if (object.ClassID==SubClass1::ID) {
    printf("The comparison works without doing a string comparison!\n");
  }
}
(May not be actual working code, but it illustrates what I mean.)

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply